This commit is contained in:
2026-09-28 23:15:47 +05:30
commit 605c6a2e58
41 changed files with 4299 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { isAuthorized } from "../lib/auth";
import { readLimited } from "../lib/body";
import { incomingWins } from "../lib/conflict";
import { isRowId, isUuid } from "../lib/ids";
import { money } from "../lib/money";
import { normalizePhone } from "../lib/phone";
import { allowRequest, clientIp, resetRateLimit } from "../lib/rate-limit";
import { SYNC_TABLES, SYNC_TABLE_NAMES } from "../lib/schema";
import { EPOCH_ISO, prepareRow, resolveCursors } from "../lib/sync";
import { isoToSql, sqlDateToIso } from "../lib/time";
import { pickWorkshopJob, stageLabel } from "../lib/workshop";
const SPEC_TABLES = [
"staff_members",
"shop_settings",
"work_days",
"attendance_records",
"short_leaves",
"overtime_logs",
"customers",
"vehicles",
"job_cards",
"odo_readings",
"job_card_events",
"part_types",
"bike_models",
"bike_groups",
"bike_group_members",
"catalog_parts",
"catalog_part_models",
"part_variants",
"job_line_items",
"invoices",
"invoice_lines",
"payments",
"demand_logs",
"sms_outbox",
"publish_outbox",
];
test("normalizePhone matches the tablet", () => {
assert.equal(normalizePhone("771234567"), "771234567");
assert.equal(normalizePhone("0771234567"), "0771234567");
assert.equal(normalizePhone("077 123 4567"), "0771234567");
assert.equal(normalizePhone("00771234567"), "0771234567");
assert.equal(normalizePhone("917712345678"), "7712345678");
assert.equal(normalizePhone(""), "");
});
test("money uses Rs and two fraction digits", () => {
assert.equal(money(500), "Rs 500.00");
assert.equal(money(0), "Rs 0.00");
assert.match(money(1500), /^Rs 1,500\.00$/);
});
test("uuid and row ids", () => {
assert.equal(isUuid("3f1c2a90-7b14-4d2e-9c11-6a0b5e8d4f21"), true);
assert.equal(isUuid("default"), false);
assert.equal(isUuid("not-a-uuid"), false);
assert.equal(isRowId("default"), true);
assert.equal(isRowId("3f1c2a90-7b14-4d2e-9c11-6a0b5e8d4f21"), true);
assert.equal(isRowId(""), false);
assert.equal(isRowId("has space"), false);
});
test("conflict rule is last write wins, equal incoming wins", () => {
const older = new Date("2026-09-28T06:30:00.000Z");
const newer = new Date("2026-09-28T07:30:00.000Z");
assert.equal(incomingWins(null, older), true);
assert.equal(incomingWins(older, newer), true);
assert.equal(incomingWins(older, new Date(older.getTime())), true);
assert.equal(incomingWins(newer, older), false);
});
test("timestamps round-trip as UTC", () => {
assert.equal(isoToSql("2026-09-28T06:30:00.000Z"), "2026-09-28 06:30:00.000");
assert.equal(sqlDateToIso("2026-09-28 06:30:00.000"), "2026-09-28T06:30:00.000Z");
assert.equal(isoToSql("not-a-date"), null);
});
test("auth requires the bearer token", () => {
const previous = process.env.SYNC_TOKEN;
process.env.SYNC_TOKEN = "test-secret-value";
try {
assert.equal(isAuthorized("Bearer test-secret-value"), true);
assert.equal(isAuthorized("Bearer wrong-secret-value"), false);
assert.equal(isAuthorized("Bearer test-secret-value extra"), false);
assert.equal(isAuthorized("Basic test-secret-value"), false);
assert.equal(isAuthorized(null), false);
} finally {
if (previous === undefined) delete process.env.SYNC_TOKEN;
else process.env.SYNC_TOKEN = previous;
}
});
test("status rate limit is 10 per minute per address", () => {
resetRateLimit();
const now = 1_000_000;
for (let i = 0; i < 10; i += 1) assert.equal(allowRequest("203.0.113.5", now), true);
assert.equal(allowRequest("203.0.113.5", now), false);
assert.equal(allowRequest("203.0.113.9", now), true);
assert.equal(allowRequest("203.0.113.5", now + 60_000), true);
});
test("client ip is the last forwarded hop", () => {
assert.equal(clientIp("1.2.3.4, 203.0.113.5"), "203.0.113.5");
assert.equal(clientIp(null), "direct");
});
test("readLimited caps the body", async () => {
const small = new Request("http://127.0.0.1/", { method: "POST", body: "abc" });
assert.equal(await readLimited(small, 10), "abc");
const big = new Request("http://127.0.0.1/", { method: "POST", body: "x".repeat(20) });
assert.equal(await readLimited(big, 10), null);
});
test("missing sync cursor is the epoch", () => {
const resolved = resolveCursors(undefined);
assert.equal(resolved.error, undefined);
assert.equal(resolved.cursors.customers.updatedAt.toISOString(), EPOCH_ISO);
assert.equal(resolved.cursors.customers.id, "");
assert.equal(Object.keys(resolved.cursors).length, SPEC_TABLES.length);
const partial = resolveCursors({
customers: { updatedAt: "2026-09-28T06:30:00.000Z", id: "abc" },
});
assert.equal(partial.cursors.customers.id, "abc");
assert.equal(partial.cursors.invoices.updatedAt.toISOString(), EPOCH_ISO);
assert.equal(resolveCursors({ customers: { updatedAt: "nope", id: "" } }).error, "invalid cursor");
});
test("prepareRow stores is_dirty itself and accepts the settings id", () => {
const settings = prepareRow(SYNC_TABLES.shop_settings, {
id: "default",
name: "100kmph",
sms_bill_template: "Bill {url}",
sms_status_template: "Status",
public_base_url: "https://example.test",
updated_at: "2026-09-28T06:30:00.000Z",
is_dirty: true,
});
assert.equal(settings.ok, true);
if (settings.ok) {
assert.equal(settings.row.columns.includes("is_dirty"), false);
assert.equal(settings.row.id, "default");
}
const customer = prepareRow(SYNC_TABLES.customers, {
id: "3f1c2a90-7b14-4d2e-9c11-6a0b5e8d4f21",
phone: "771234567",
updated_at: "2026-09-28T06:30:00.000Z",
pin_hash: "should-not-stick",
});
assert.equal(customer.ok, false);
});
test("workshop job prefers the newest open stage", () => {
const picked = pickWorkshopJob([
{ id: "a", stage: "billed", createdAt: "2026-09-28T08:00:00.000Z" },
{ id: "b", stage: "in_repair", createdAt: "2026-09-28T07:00:00.000Z" },
{ id: "c", stage: "received", createdAt: "2026-09-28T06:00:00.000Z" },
]);
assert.equal(picked?.id, "b");
assert.equal(
pickWorkshopJob([{ id: "a", stage: "billed", createdAt: "2026-09-28T08:00:00.000Z" }]),
null,
);
assert.equal(stageLabel("quality_check"), "Quality Check");
assert.equal(stageLabel("in_repair"), "In Repair");
});
test("schema matches the migration", () => {
const sql = readFileSync("db/001_init.sql", "utf8");
assert.deepEqual(SYNC_TABLE_NAMES, SPEC_TABLES);
assert.equal(sql.includes("FOREIGN KEY"), false);
assert.match(sql, /Two tablets incrementing this will clash/);
assert.match(sql, /CREATE TABLE IF NOT EXISTS bill_snapshots/);
for (const name of SPEC_TABLES) {
const match = sql.match(new RegExp(`CREATE TABLE IF NOT EXISTS ${name} \\(([\\s\\S]*?)\\n\\)`));
assert.ok(match, name);
const body = match[1];
assert.match(body, /updated_at, id/);
for (const column of SYNC_TABLES[name].columns) {
assert.match(body, new RegExp(`\\b${column.name}\\b`));
}
const hasPin = SYNC_TABLES[name].columns.some((column) => column.name === "pin_hash");
assert.equal(hasPin, name === "staff_members");
}
const customers = sql.match(/CREATE TABLE IF NOT EXISTS customers \(([\s\S]*?)\n\)/);
assert.match(customers?.[1] ?? "", /idx_customers_phone \(phone\)/);
const invoices = sql.match(/CREATE TABLE IF NOT EXISTS invoices \(([\s\S]*?)\n\)/);
assert.match(invoices?.[1] ?? "", /uq_invoices_public_token \(public_token\)/);
const jobs = sql.match(/CREATE TABLE IF NOT EXISTS job_cards \(([\s\S]*?)\n\)/);
assert.match(jobs?.[1] ?? "", /idx_job_cards_customer \(customer_id\)/);
assert.match(jobs?.[1] ?? "", /idx_job_cards_vehicle \(vehicle_id\)/);
});
test("public lookup code does not select private columns", () => {
const status = readFileSync("lib/status.ts", "utf8");
for (const word of ["engineer_notes", "complaint", "pin_hash", "sale_price", "unit_price", "qty_on_hand"]) {
assert.equal(status.includes(word), false, word);
}
const sync = readFileSync("lib/sync.ts", "utf8");
assert.equal(sync.includes("console."), false);
});