feat(landing): 거래처 사용법 6단계 + 화면 미리보기 섹션 추가
- 가입→검색→장바구니→승인→메일→정산까지 단계별 카드 - 장바구니 미리보기 + 자동발송 메일 미리보기 추가 - 우측 상단 회원가입/로그인 버튼은 기존 유지 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// E2E 테스트: 가입 → 시드 데이터 → 발주 → 승인 + 메일 발송
|
||||
// 전제: dev 서버 실행 중 (localhost:3000), DB 마이그레이션 완료
|
||||
// 사용법: node scripts/test-e2e.mjs
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import pg from "pg";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const envPath = path.join(__dirname, "..", ".env.development");
|
||||
if (fs.existsSync(envPath)) {
|
||||
for (const line of fs.readFileSync(envPath, "utf-8").split(/\r?\n/)) {
|
||||
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
||||
if (m) process.env[m[1]] ??= m[2].replace(/^["']|["']$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
const BASE = "http://localhost:3000";
|
||||
const TEST_EMAIL = "chpark@wace.me";
|
||||
const TEST_PASSWORD = "test1234abcd";
|
||||
|
||||
let cookieJar = "";
|
||||
function setCookies(res) {
|
||||
const sc = res.headers.getSetCookie?.() || res.headers.raw?.()["set-cookie"] || [];
|
||||
for (const c of sc) {
|
||||
const kv = c.split(";")[0];
|
||||
cookieJar = cookieJar
|
||||
? cookieJar.split("; ").filter((p) => !p.startsWith(kv.split("=")[0] + "=")).concat(kv).join("; ")
|
||||
: kv;
|
||||
}
|
||||
}
|
||||
async function api(path, init = {}) {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(cookieJar ? { Cookie: cookieJar } : {}),
|
||||
...(init.headers ?? {}),
|
||||
},
|
||||
});
|
||||
setCookies(res);
|
||||
const text = await res.text();
|
||||
let json;
|
||||
try { json = JSON.parse(text); } catch { json = { raw: text }; }
|
||||
return { status: res.status, body: json };
|
||||
}
|
||||
|
||||
const log = (...a) => console.log("[e2e]", ...a);
|
||||
const fail = (msg) => { console.error("[e2e] ✖", msg); process.exit(1); };
|
||||
|
||||
// ===== 1. DB 정리 + 시드 (테스트 데이터) =====
|
||||
log("1. DB 정리 + 시드");
|
||||
const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
|
||||
await client.connect();
|
||||
|
||||
// 기존 테스트 사용자 삭제 (재실행 가능하게)
|
||||
await client.query(`DELETE FROM momo_order_items WHERE order_objid IN (SELECT objid FROM momo_orders WHERE customer_objid IN (SELECT objid FROM momo_users WHERE email = $1))`, [TEST_EMAIL]);
|
||||
await client.query(`DELETE FROM momo_orders WHERE customer_objid IN (SELECT objid FROM momo_users WHERE email = $1)`, [TEST_EMAIL]);
|
||||
await client.query(`DELETE FROM momo_users WHERE email = $1`, [TEST_EMAIL]);
|
||||
|
||||
// 품목 / 재고 시드 (테스트 품목 3개 + 본사창고 재고 100개씩)
|
||||
const items = [
|
||||
{ code: "TEST-EGG-01", name: "M 유정란", price: 10000, taxFree: "Y" },
|
||||
{ code: "TEST-CHKN-01", name: "M 꽃계탕", price: 4500, taxFree: "Y" },
|
||||
{ code: "TEST-CLEAN-01", name: "빨강 탈취제", price: 9200, taxFree: "N" },
|
||||
];
|
||||
for (const it of items) {
|
||||
await client.query(
|
||||
`INSERT INTO momo_items (objid, item_code, item_name, unit, unit_price, is_tax_free, status, regdate)
|
||||
VALUES ($1, $2, $3, 'EA', $4, $5, 'ACTIVE', NOW())
|
||||
ON CONFLICT (item_code) DO UPDATE SET unit_price = EXCLUDED.unit_price, is_tax_free = EXCLUDED.is_tax_free, status='ACTIVE', is_del='N'`,
|
||||
[`TEST-${it.code}`, it.code, it.name, it.price, it.taxFree]
|
||||
);
|
||||
}
|
||||
const wh = await client.query(`SELECT objid FROM momo_warehouses WHERE wh_type='STOCK' LIMIT 1`);
|
||||
const whObjid = wh.rows[0].objid;
|
||||
for (const it of items) {
|
||||
await client.query(
|
||||
`INSERT INTO momo_stocks (objid, wh_objid, item_objid, qty, update_date)
|
||||
VALUES ($1, $2, (SELECT objid FROM momo_items WHERE item_code = $3), 100, NOW())
|
||||
ON CONFLICT (wh_objid, item_objid) DO UPDATE SET qty = 100, update_date = NOW()`,
|
||||
[`TEST-STK-${it.code}`, whObjid, it.code]
|
||||
);
|
||||
}
|
||||
log(" 품목 3개 + 재고 100개씩 시드 완료");
|
||||
|
||||
// ===== 2. 가입 =====
|
||||
log("2. 회원가입");
|
||||
const sup = await api("/api/auth/signup", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: TEST_EMAIL,
|
||||
password: TEST_PASSWORD,
|
||||
companyName: "테스트거래처(wace)",
|
||||
ceoName: "박철현",
|
||||
bizNo: "123-45-67890",
|
||||
phone: "010-1234-5678",
|
||||
}),
|
||||
});
|
||||
if (!sup.body.success) fail(`가입 실패: ${JSON.stringify(sup.body)}`);
|
||||
log(` ✔ 가입 성공 (USER 세션 발급, cookie=${cookieJar.slice(0, 30)}...)`);
|
||||
|
||||
// ===== 3. 품목 조회 (USER 세션) =====
|
||||
log("3. 품목 검색 (USER)");
|
||||
const list = await api("/api/m/items/list", { method: "POST", body: JSON.stringify({ keyword: "TEST" }) });
|
||||
const visible = (list.body.RESULTLIST || []).filter((r) => r.ITEM_CODE.startsWith("TEST-"));
|
||||
if (visible.length !== 3) fail(`품목 조회 실패: ${visible.length}개 (기대 3개)`);
|
||||
log(` ✔ ${visible.length}개 품목 노출, 재고 ${visible[0].STOCK_QTY}`);
|
||||
|
||||
// ===== 4. 발주 작성 =====
|
||||
log("4. 발주 요청");
|
||||
const order = await api("/api/m/orders/save", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
lines: [
|
||||
{ itemObjid: visible.find((x) => x.ITEM_CODE === "TEST-EGG-01").OBJID, qty: 30 },
|
||||
{ itemObjid: visible.find((x) => x.ITEM_CODE === "TEST-CHKN-01").OBJID, qty: 20 },
|
||||
{ itemObjid: visible.find((x) => x.ITEM_CODE === "TEST-CLEAN-01").OBJID, qty: 11 },
|
||||
],
|
||||
memo: "E2E 테스트 발주",
|
||||
}),
|
||||
});
|
||||
if (!order.body.success) fail(`발주 실패: ${JSON.stringify(order.body)}`);
|
||||
log(` ✔ 발주번호: ${order.body.orderNo}, objId: ${order.body.objId}`);
|
||||
|
||||
// ===== 5. 어드민 로그인 (시드 관리자) =====
|
||||
log("5. 관리자 로그인");
|
||||
cookieJar = ""; // USER 세션 클리어
|
||||
const adminLogin = await api("/api/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ userId: "admin@momo.com", password: "admin1234" }),
|
||||
});
|
||||
if (!adminLogin.body.success) {
|
||||
// 시드 비밀번호 해시가 환경에 따라 다를 수 있어 → DB에서 비밀번호 재설정
|
||||
log(" 시드 비번 불일치 — bcrypt 재설정");
|
||||
const bcrypt = (await import("bcryptjs")).default;
|
||||
const hash = await bcrypt.hash("admin1234", 10);
|
||||
await client.query(`UPDATE momo_users SET password_hash = $1 WHERE email = 'admin@momo.com'`, [hash]);
|
||||
const retry = await api("/api/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ userId: "admin@momo.com", password: "admin1234" }),
|
||||
});
|
||||
if (!retry.body.success) fail(`관리자 로그인 실패: ${JSON.stringify(retry.body)}`);
|
||||
}
|
||||
log(` ✔ 관리자 세션 발급`);
|
||||
|
||||
// ===== 6. 승인 + 메일 발송 =====
|
||||
log("6. 발주 승인 (재고차감 + 메일발송)");
|
||||
const approve = await api("/api/m/orders/approve", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ objid: order.body.objId }),
|
||||
});
|
||||
if (!approve.body.success) fail(`승인 실패: ${JSON.stringify(approve.body)}`);
|
||||
log(` ✔ 승인 완료, mailSent=${approve.body.mailSent}, mailError=${approve.body.mailError ?? "(none)"}`);
|
||||
|
||||
// ===== 7. 결과 검증 =====
|
||||
log("7. 후속 검증");
|
||||
const r1 = await client.query(`SELECT status, total_amount, total_taxfree, total_taxable FROM momo_orders WHERE objid = $1`, [order.body.objId]);
|
||||
log(` 주문 상태: ${r1.rows[0].status} (기대 APPROVED)`);
|
||||
log(` 금액: 면세 ${r1.rows[0].total_taxfree} / 과세 ${r1.rows[0].total_taxable} / 합계 ${r1.rows[0].total_amount}`);
|
||||
if (r1.rows[0].status !== "APPROVED") fail("주문 상태 불일치");
|
||||
|
||||
const r2 = await client.query(`SELECT qty FROM momo_stocks WHERE wh_objid = $1 AND item_objid = (SELECT objid FROM momo_items WHERE item_code = 'TEST-EGG-01')`, [whObjid]);
|
||||
log(` M 유정란 재고: ${r2.rows[0].qty} (기대 70 = 100-30)`);
|
||||
if (Number(r2.rows[0].qty) !== 70) fail("재고 차감 불일치");
|
||||
|
||||
const r3 = await client.query(`SELECT to_email, subject, status, error_msg FROM momo_mail_logs WHERE ref_objid = $1 ORDER BY regdate DESC LIMIT 1`, [order.body.objId]);
|
||||
log(` 메일 로그: to=${r3.rows[0].to_email}, status=${r3.rows[0].status}, subject=${r3.rows[0].subject}`);
|
||||
if (r3.rows[0].error_msg) log(` 메일 에러: ${r3.rows[0].error_msg}`);
|
||||
|
||||
log("✔ E2E 테스트 완료");
|
||||
await client.end();
|
||||
Reference in New Issue
Block a user