import { randomUUID } from "node:crypto"; import process from "node:process"; import { createInterface } from "node:readline"; import pg from "pg"; import { buildApp } from "../dist/src/app.js"; import { PostgresCondoRepository } from "../dist/src/postgres-repository.js"; const { Pool } = pg; async function readStandardInput() { const lines = createInterface({ input: process.stdin, terminal: false }); for await (const line of lines) { lines.close(); return line.trim(); } throw Object.assign(new Error("Missing connection input"), { code: "MISSING_CONNECTION_INPUT" }); } function safeError(error) { return { ok: false, errorCode: typeof error?.code === "string" ? error.code : "API_WRITE_SMOKE_FAILED" }; } const checks = {}; let pool; let app; let ownerId; let periodId; let testRoomNo; let confirmationNo = "997000001"; let baselineCounts; try { const input = JSON.parse(await readStandardInput()); pool = new Pool({ host: input.host, port: input.port, user: input.user, password: input.password, database: input.database, ssl: input.ssl ? { rejectUnauthorized: false } : undefined, max: 3, application_name: "condon_api_write_smoke", connectionTimeoutMillis: 10_000, options: "-c statement_timeout=30000 -c lock_timeout=5000" }); const initialResult = await pool.query(` SELECT (SELECT count(*)::integer FROM condon.owner_accounts) AS owners, (SELECT count(*)::integer FROM condon.entitlement_periods) AS periods, (SELECT count(*)::integer FROM condon.usage_records) AS usage, (SELECT count(*)::integer FROM condon.entitlement_ledger) AS ledger, (SELECT count(*)::integer FROM condon.bookings) AS bookings `); baselineCounts = initialResult.rows[0]; checks.initiallyAvailable = true; ownerId = randomUUID(); periodId = randomUUID(); testRoomNo = `T${ownerId.replaceAll("-", "").slice(0, 12)}`; const setupClient = await pool.connect(); try { await setupClient.query("BEGIN"); await setupClient.query( ` INSERT INTO condon.owner_accounts ( id, account_no, transfer_date, owner_name, room_no, purchased_room_type_code, unit_no, member_no ) VALUES ($1, NULL, NULL, 'API Write Test Owner', $2, 'RM1', 'TEST/3', 'TEST-3') `, [ownerId, testRoomNo] ); await setupClient.query( ` SELECT condon.open_entitlement_period( $1::uuid, $2::uuid, 2097::smallint, $3::uuid, NULL::uuid ) `, [periodId, ownerId, randomUUID()] ); await setupClient.query("COMMIT"); } catch (error) { await setupClient.query("ROLLBACK").catch(() => undefined); throw error; } finally { setupClient.release(); } const repository = new PostgresCondoRepository(pool); app = await buildApp({ repository, logger: false }); const idempotencyKey = randomUUID(); const payload = { ownerAccountId: ownerId, confirmationNo, checkIn: "2097-03-01", checkOut: "2097-03-03", usedRoomType: "RM1", remark: "API write smoke", idempotencyKey }; const created = await app.inject({ method: "POST", url: "/usage-records", payload }); const createdBody = created.json(); checks.apiCreate = created.statusCode === 201 && createdBody.ownerAccountId === ownerId && createdBody.night === 2 && createdBody.use === 2 && createdBody.balance === 13; const retried = await app.inject({ method: "POST", url: "/usage-records", payload }); const retriedBody = retried.json(); checks.apiIdempotency = retried.statusCode === 201 && retriedBody.id === createdBody.id && retriedBody.balance === 13; const dashboard = await app.inject({ method: "GET", url: "/dashboard?year=2097" }); checks.apiDashboardReflectsWrite = dashboard.statusCode === 200 && dashboard.json().ownerRooms === Number(baselineCounts.owners) + 1 && dashboard.json().remainingPrivileges === 13 && dashboard.json().used === 2; const usage = await app.inject({ method: "GET", url: `/usage-records?ownerAccountId=${ownerId}` }); checks.apiHistoryReflectsWrite = usage.statusCode === 200 && usage.json().total === 1 && usage.json().items[0].confirmationNo === confirmationNo; const ownerBeyondPage = await app.inject({ method: "GET", url: `/owner-accounts?q=${encodeURIComponent(testRoomNo)}&year=2097&page=2&pageSize=1` }); const usageBeyondPage = await app.inject({ method: "GET", url: `/usage-records?ownerAccountId=${ownerId}&page=2&pageSize=1` }); checks.paginationTotalsSurviveEmptyPage = ownerBeyondPage.statusCode === 200 && ownerBeyondPage.json().total === 1 && ownerBeyondPage.json().items.length === 0 && usageBeyondPage.statusCode === 200 && usageBeyondPage.json().total === 1 && usageBeyondPage.json().items.length === 0; } catch (error) { process.stdout.write(`${JSON.stringify(safeError(error), null, 2)}\n`); process.exitCode = 1; } finally { if (pool && ownerId && periodId) { const cleanup = await pool.connect().catch(() => undefined); if (cleanup) { try { await cleanup.query("BEGIN"); await cleanup.query( "DELETE FROM condon.entitlement_ledger WHERE entitlement_period_id = $1", [periodId] ); await cleanup.query( "DELETE FROM condon.usage_records WHERE owner_account_id = $1", [ownerId] ); await cleanup.query( "DELETE FROM condon.bookings WHERE confirmation_no = $1", [confirmationNo] ); await cleanup.query( "DELETE FROM condon.entitlement_periods WHERE id = $1", [periodId] ); await cleanup.query( "DELETE FROM condon.owner_accounts WHERE id = $1", [ownerId] ); await cleanup.query("COMMIT"); checks.cleanup = true; } catch { await cleanup.query("ROLLBACK").catch(() => undefined); checks.cleanup = false; process.exitCode = 1; } finally { cleanup.release(); } } } if (pool) { try { const finalResult = await pool.query(` SELECT (SELECT count(*)::integer FROM condon.owner_accounts) AS owners, (SELECT count(*)::integer FROM condon.entitlement_periods) AS periods, (SELECT count(*)::integer FROM condon.usage_records) AS usage, (SELECT count(*)::integer FROM condon.entitlement_ledger) AS ledger, (SELECT count(*)::integer FROM condon.bookings) AS bookings `); checks.finalBusinessTablesPreserved = Object.entries(baselineCounts).every( ([key, value]) => Number(finalResult.rows[0][key]) === Number(value) ); } catch { checks.finalBusinessTablesEmpty = false; process.exitCode = 1; } } if (app) { await app.close().catch(() => { process.exitCode = 1; }); } else if (pool) { await pool.end().catch(() => { process.exitCode = 1; }); } if (Object.keys(checks).length > 0) { const ok = Object.values(checks).every(Boolean); process.stdout.write(`${JSON.stringify({ ok, checks }, null, 2)}\n`); if (!ok) process.exitCode = 1; } }