257 lines
7.3 KiB
JavaScript
257 lines
7.3 KiB
JavaScript
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;
|
|
}
|
|
}
|