feat: add Condo owner desk frontend and backend
This commit is contained in:
256
backend/scripts/api-write-smoke.mjs
Normal file
256
backend/scripts/api-write-smoke.mjs
Normal file
@@ -0,0 +1,256 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user