feat: add database seed script and utility scripts
- src/db/seed.ts: comprehensive seed data with idempotent upserts - 5 users (system, gjermund, operator, technician, analyst) - 5 queues (Support Desk, Operations, IT Infrastructure, Facilities, Field Ops) - 1 lifecycle (Demo service lifecycle with new→open→in_progress→resolved→closed) - 5 custom fields (impact, location, channel, urgency, outcome) with short keys - 10 realistic support tickets with varied statuses, custom fields, and history - 3 scrips (OnCreate email, OnResolve custom field, customer notification) - 2 templates (auto-response, resolve notification) - --reset flag to truncate all data before seeding - scripts/smoke-test.ts: API smoke tests - scripts/watch-frontend.sh: frontend dev helper Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
112
scripts/smoke-test.ts
Normal file
112
scripts/smoke-test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
const backendUrl = process.env.BACKEND_URL ?? 'http://127.0.0.1:9876';
|
||||
const frontendUrl = process.env.FRONTEND_URL ?? 'http://127.0.0.1:3100';
|
||||
|
||||
interface Ticket {
|
||||
id: number;
|
||||
subject: string;
|
||||
}
|
||||
|
||||
interface Queue {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
id: string;
|
||||
ticket_id: number;
|
||||
transaction_type: string;
|
||||
}
|
||||
|
||||
async function requestJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${url} returned ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function requestOk(url: string): Promise<void> {
|
||||
const response = await fetch(url, { method: 'HEAD' });
|
||||
if (!response.ok) {
|
||||
throw new Error(`${url} returned ${response.status} ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function check(name: string, fn: () => Promise<void>): Promise<void> {
|
||||
try {
|
||||
await fn();
|
||||
console.log(`ok ${name}`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error(`fail ${name}`);
|
||||
console.error(` ${message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let ticketForDetail: Ticket | null = null;
|
||||
|
||||
await check('backend health', async () => {
|
||||
const health = await requestJson<{ status: string }>(`${backendUrl}/health`);
|
||||
if (health.status !== 'ok') {
|
||||
throw new Error(`expected status ok, got ${JSON.stringify(health)}`);
|
||||
}
|
||||
});
|
||||
|
||||
await check('queues exist', async () => {
|
||||
const queues = await requestJson<Queue[]>(`${backendUrl}/queues`);
|
||||
if (queues.length < 1) {
|
||||
throw new Error('expected at least one queue');
|
||||
}
|
||||
});
|
||||
|
||||
await check('tickets exist', async () => {
|
||||
const tickets = await requestJson<Ticket[]>(`${backendUrl}/tickets`);
|
||||
if (tickets.length < 1) {
|
||||
throw new Error('expected at least one ticket');
|
||||
}
|
||||
ticketForDetail = tickets.find((ticket) => ticket.subject.includes('VPN access')) ?? tickets[0] ?? null;
|
||||
});
|
||||
|
||||
await check('ticket detail has activity', async () => {
|
||||
if (!ticketForDetail) {
|
||||
throw new Error('no ticket available for detail check');
|
||||
}
|
||||
const transactions = await requestJson<Transaction[]>(
|
||||
`${backendUrl}/tickets/${ticketForDetail.id}/transactions`,
|
||||
);
|
||||
if (transactions.length < 1) {
|
||||
throw new Error(`expected ticket ${ticketForDetail.id} to have transactions`);
|
||||
}
|
||||
});
|
||||
|
||||
await check('frontend index responds', async () => {
|
||||
await requestOk(frontendUrl);
|
||||
});
|
||||
|
||||
await check('frontend ticket detail responds', async () => {
|
||||
if (!ticketForDetail) {
|
||||
throw new Error('no ticket available for frontend detail check');
|
||||
}
|
||||
await requestOk(`${frontendUrl}/tickets/${ticketForDetail.id}`);
|
||||
});
|
||||
|
||||
await check('frontend api proxy responds', async () => {
|
||||
const health = await requestJson<{ status: string }>(`${frontendUrl}/api/health`);
|
||||
if (health.status !== 'ok') {
|
||||
throw new Error(`expected status ok, got ${JSON.stringify(health)}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (process.exitCode) {
|
||||
process.exit(process.exitCode);
|
||||
}
|
||||
|
||||
console.log('Smoke test passed');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
15
scripts/watch-frontend.sh
Normal file
15
scripts/watch-frontend.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Watch for source changes and auto-rebuild + restart Tessera frontend
|
||||
DIR="/home/gjermund/projects/tessera/web/src"
|
||||
LAST_BUILD=0
|
||||
|
||||
echo "Watching $DIR for changes..."
|
||||
|
||||
inotifywait -m -r -e modify,create,delete "$DIR" --format '%w%f' 2>/dev/null | while read FILE; do
|
||||
NOW=$(date +%s)
|
||||
if [ $((NOW - LAST_BUILD)) -gt 3 ]; then
|
||||
echo "[$(date +%H:%M:%S)] Change detected, rebuilding..."
|
||||
cd /home/gjermund/projects/tessera/web && npx next build 2>&1 | tail -1
|
||||
LAST_BUILD=$NOW
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user