Initial commit
This commit is contained in:
48
scripts/smoke-cleanup.mjs
Normal file
48
scripts/smoke-cleanup.mjs
Normal file
@ -0,0 +1,48 @@
|
||||
import { env as processEnv } from "node:process";
|
||||
import { Pool } from "pg";
|
||||
|
||||
function required(value, fallback) {
|
||||
return value || fallback;
|
||||
}
|
||||
|
||||
const pool = processEnv.DATABASE_URL
|
||||
? new Pool({ connectionString: processEnv.DATABASE_URL })
|
||||
: new Pool({
|
||||
host: required(processEnv.PGHOST, "127.0.0.1"),
|
||||
port: Number(required(processEnv.PGPORT, "5432")),
|
||||
user: required(processEnv.PGUSER, "postgres"),
|
||||
password: processEnv.PGPASSWORD || "",
|
||||
database: required(processEnv.PGDATABASE, "qris_soundbox_platform")
|
||||
});
|
||||
|
||||
async function main() {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
const txResult = await client.query("DELETE FROM transactions WHERE partner_reference LIKE 'PR-%' RETURNING id");
|
||||
const devResult = await client.query("DELETE FROM devices WHERE device_code LIKE 'DEV-%' RETURNING id");
|
||||
const merchantResult = await client.query("DELETE FROM merchants WHERE legal_name LIKE 'Smoke Merchant %' RETURNING id");
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
console.log(JSON.stringify({
|
||||
transactions_deleted: txResult.rowCount,
|
||||
devices_deleted: devResult.rowCount,
|
||||
merchants_deleted: merchantResult.rowCount,
|
||||
note: "outlets/terminals are removed via merchant cascade"
|
||||
}));
|
||||
} catch (error) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error("cleanup failed", error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
client.release();
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("cleanup failed", error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
167
scripts/smoke.mjs
Normal file
167
scripts/smoke.mjs
Normal file
@ -0,0 +1,167 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
|
||||
const PORT = process.env.PORT || "3100";
|
||||
const BASE = process.env.BASE_URL || `http://127.0.0.1:${PORT}`;
|
||||
const ADMIN_TOKEN = process.env.ADMIN_TOKEN || "admin-dev-token";
|
||||
const DEVICE_TOKEN = process.env.DEVICE_TOKEN || "device-dev-token";
|
||||
const SECRET = process.env.INTEGRATION_WEBHOOK_SECRET || "dev-callback-secret";
|
||||
|
||||
function short(data) {
|
||||
const json = typeof data === 'string' ? data : JSON.stringify(data || {});
|
||||
return json.length > 180 ? `${json.slice(0, 180)}...` : json;
|
||||
}
|
||||
|
||||
async function req(path, options = {}) {
|
||||
const response = await fetch(`${BASE}${path}`, {
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {})
|
||||
},
|
||||
body: Object.prototype.hasOwnProperty.call(options, 'body') ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let body = null;
|
||||
try {
|
||||
body = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
body = text;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`${options._label || path} failed: ${response.status}`);
|
||||
}
|
||||
|
||||
console.log(`${options._label || `${options.method || 'GET'} ${path}`} => ${response.status} ${short(body)}`);
|
||||
return body;
|
||||
}
|
||||
|
||||
async function reqAdmin(path, opts = {}) {
|
||||
return req(path, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${ADMIN_TOKEN}` } });
|
||||
}
|
||||
|
||||
async function reqDevice(path, opts = {}) {
|
||||
return req(path, { ...opts, headers: { ...(opts.headers || {}), Authorization: `Bearer ${DEVICE_TOKEN}` } });
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await req('/health', { _label: 'GET /health' });
|
||||
await req('/admin/login', { method: 'POST', body: { username: 'admin', password: 'admin' }, _label: 'POST /admin/login' });
|
||||
|
||||
await reqAdmin('/admin/seed/status', { _label: 'GET /admin/seed/status' });
|
||||
const ts = Date.now();
|
||||
|
||||
const merchant = await reqAdmin('/admin/merchants', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
legal_name: `Smoke Merchant ${ts}`,
|
||||
brand_name: `SMK-${ts}`,
|
||||
settlement_account_reference: `bank:${ts}`,
|
||||
settlement_account_type: 'merchant_bank_account',
|
||||
payout_mode: 'merchant_direct'
|
||||
},
|
||||
_label: 'POST /admin/merchants'
|
||||
});
|
||||
|
||||
const merchantId = merchant?.data?.id;
|
||||
const outlet = await reqAdmin(`/admin/merchants/${merchantId}/outlets`, {
|
||||
method: 'POST',
|
||||
body: { name: `Outlet ${ts}` },
|
||||
_label: 'POST /admin/merchants/:id/outlets'
|
||||
});
|
||||
|
||||
const outletId = outlet?.data?.id;
|
||||
const terminal = await reqAdmin(`/admin/outlets/${outletId}/terminals`, {
|
||||
method: 'POST',
|
||||
body: { terminal_code: `TERM-${ts}`, qr_mode: 'static' },
|
||||
_label: 'POST /admin/outlets/:id/terminals'
|
||||
});
|
||||
|
||||
const terminalId = terminal?.data?.id;
|
||||
const device = await reqAdmin('/admin/devices', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
device_code: `DEV-${ts}`,
|
||||
vendor: 'acme',
|
||||
model: 'v1',
|
||||
communication_mode: 'mqtt',
|
||||
status: 'active'
|
||||
},
|
||||
_label: 'POST /admin/devices'
|
||||
});
|
||||
|
||||
const deviceId = device?.data?.id;
|
||||
await reqAdmin(`/admin/devices/${deviceId}/bind`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
merchant_id: merchantId,
|
||||
outlet_id: outletId,
|
||||
terminal_id: terminalId
|
||||
},
|
||||
_label: 'POST /admin/devices/:id/bind'
|
||||
});
|
||||
|
||||
await reqDevice('/device/heartbeat', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
device_id: deviceId,
|
||||
timestamp: new Date().toISOString(),
|
||||
firmware_version: '1.2.3',
|
||||
network_strength: 88,
|
||||
battery_level: 77,
|
||||
state: 'idle'
|
||||
},
|
||||
_label: 'POST /device/heartbeat'
|
||||
});
|
||||
|
||||
const tx = await reqAdmin('/admin/transactions', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
partner_reference: `PR-${ts}`,
|
||||
merchant_id: merchantId,
|
||||
outlet_id: outletId,
|
||||
terminal_id: terminalId,
|
||||
device_id: deviceId,
|
||||
amount: 19900,
|
||||
currency: 'IDR',
|
||||
qr_mode: 'static',
|
||||
initiation_mode: 'static',
|
||||
status: 'initiated'
|
||||
},
|
||||
_label: 'POST /admin/transactions'
|
||||
});
|
||||
|
||||
const txId = tx?.data?.id;
|
||||
const callback = {
|
||||
partner_reference: `PR-${ts}`,
|
||||
partner_txn_id: `PTX-${ts}`,
|
||||
amount: 19900,
|
||||
currency: 'IDR',
|
||||
payment_status: 'paid',
|
||||
status: 'paid',
|
||||
paid_at: new Date().toISOString()
|
||||
};
|
||||
const signature = createHmac('sha256', SECRET).update(JSON.stringify(callback)).digest('hex');
|
||||
|
||||
await req('/integrations/qris/callback', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Partner-Signature': signature },
|
||||
body: { ...callback, signature },
|
||||
_label: 'POST /integrations/qris/callback'
|
||||
});
|
||||
|
||||
await reqAdmin(`/admin/transactions/${txId}`, { _label: 'GET /admin/transactions/:id' });
|
||||
await reqAdmin(`/admin/transactions/${txId}/events`, { _label: 'GET /admin/transactions/:id/events' });
|
||||
await reqAdmin(`/admin/transactions/${txId}/heartbeats`, { _label: 'GET /admin/transactions/:id/heartbeats' });
|
||||
await reqAdmin(`/admin/devices/${deviceId}/heartbeats`, { _label: 'GET /admin/devices/:id/heartbeats' });
|
||||
await reqAdmin('/admin/notifications/failed', { _label: 'GET /admin/notifications/failed' });
|
||||
await reqAdmin(`/admin/transactions/${txId}/retry-notification`, {
|
||||
method: 'POST',
|
||||
body: {},
|
||||
_label: 'POST /admin/transactions/:id/retry-notification'
|
||||
});
|
||||
await reqAdmin('/admin/dashboard/summary', { _label: 'GET /admin/dashboard/summary' });
|
||||
|
||||
console.log(`Smoke point 4 flow done. tx=${txId} device=${deviceId}`);
|
||||
})();
|
||||
Reference in New Issue
Block a user