CI - Test & Build / Lint & Type Check (push) Canceled after 0s
CI - Test & Build / Backend Unit Tests (push) Canceled after 0s
CI - Test & Build / Frontend Build (push) Canceled after 0s
CI - Test & Build / Security Scan (push) Canceled after 0s
CI - Test & Build / E2E Tests (Playwright) (push) Canceled after 0s
CI - Test & Build / CI Summary (push) Canceled after 0s
* feat: move login credentials to a DB-backed users table with an admin management page Replaces the hardcoded AUTH_USERNAME/AUTH_PASSWORD login check with a new auth_accounts table (bcrypt-hashed passwords, admin/user roles). Adds admin-only /api/users CRUD routes and a "Brugere" admin page in the frontend for managing logins without redeploying. Removes the unused, unmounted duplicate login route in src/routes/auth.js. * docs: add architecture codemaps with diagrams for the whole system Adds codemaps/architecture.md, backend.md, frontend.md, and data.md — Mermaid-diagrammed design documentation verified against the live codebase and database rather than assumed from CLAUDE.md. Covers the unified-server.js request flow (mounted routers + ~183 inline routes), 68 backend services grouped by domain, the frontend's state-driven view-switch (no React Router in practice despite BrowserRouter being present), and the full 122-table DB schema with the auth_accounts vs unrelated users table naming trap flagged explicitly. Links added from the root README. Co-Authored-By: Claude Sonnet 5 <[email protected]> * feat: ship canonical roof quote workflow * fix: keep migration dry-run idempotent * [verified] feat: complete Smart Pakker management * [verified] fix: ignore blank task dependencies * [verified] fix: align package duplication with schema * [verified] fix: enforce Discord status limits * [verified] fix: link Smart Pakke materials safely * [verified] fix: harden material link review * [verified] feat: improve material matching * fix: scope pitch validation to roof packages * fix: support canonical snapshots on production schema * [verified] fix: hide internal package metadata from PDF * [verified] feat: deliver sales-ready customer PDF * [verified] feat: ship sales-ready PDF with AI overview * [verified] fix: authenticate project list requests * [verified] fix: refresh project-list authentication * [verified] fix: open existing project details * [verified] fix: keep roof components searchable in builder * [verified] fix: expose all Smart Package categories * [verified] fix: authenticate project creation * [verified] feat: make Smart Pakker the universal project flow * [verified] feat: preview Smart Package contents * [verified] test: keep generic release isolated from downpipe work * feat: add first-class Smart Pakke rentals * [verified] feat: add gutter and downpipe smart packages * [verified] fix: prepare six-house gutter quote flow * [verified] fix: open generic quotes without roof geometry * [verified] fix: review generic quotes with authenticated APIs * [verified] fix: calculate generic Smart Package quotes * [verified] fix: return generic calculation breakdown * feat: checkpoint generic signed snapshot validation with red-green tests * feat: complete fail-closed generic quote approval and customer PDF flow * feat: use generic signed snapshot in final review * feat: redesign generic quote final review * fix: harden generic review summaries * feat: add auditable six-house package basis * [verified] feat: finish auditable Smart Pakke UI * [verified] fix: bind auditable quantity and price bases * [verified] fix: keep six-house basis across package versions * [verified] fix: complete smart package discovery management * [verified] fix: simplify composition and generic scope * [verified] test: keep explicit roof contracts fail closed * [verified] fix: harden generic quote snapshots * fix: make generic quote delivery customer safe * [verified] fix: secure package catalog reads * [verified] fix: close workspace provenance blockers * fix: harden customer document language boundary * [verified] fix: secure smart package internal reads * fix: version package child mutations atomically * feat: add generic customer quote text flow * [verified] fix: allow manual customer numbers * [verified] fix: expose optional roof geometry * [verified] fix: rebase hydrated packages after geometry edits * [verified] feat: add free editable site area map * [verified] fix: harden map recovery and geocoding gate * fix: bind map quantities to authoritative geometry * fix: release geocoder lock before dispatch * fix: separate roof and site geometry provenance * fix: revoke stale admin authorization * fix: migrate task geometry basis * fix: make backend CI dependency-complete * ci: seed isolated e2e login account * fix: allow clean database bootstrap * fix: skip indexes for optional tables * test: use canonical mansard geometry in e2e * [verified] fix(auth): enforce live operator boundary * fix: fail close Ordrestyring offer transport * fix(frontend): authenticate customer project requests * fix: align canonical roof type contract * [verified] fix: reconcile legacy package labor safely * [verified] fix: audit site geometry deletion * docs: add PR 31 reviewer guide * docs: synchronize Obsidian vault * docs: sync integrated reviewer guide to Obsidian * ci: seed isolated auth account explicitly * fix: close offer bootstrap and service readiness gaps * fix: authenticate protected package callers * fix: provision initial admin and disable generic send * [verified] fix: close final quote release blockers * [verified] fix: seed gutter packages before deployment --------- Co-authored-by: alexpolo1 <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]>
107 lines
4.1 KiB
JavaScript
107 lines
4.1 KiB
JavaScript
const {
|
|
buildDiscordPayload,
|
|
discordPayloadViolations,
|
|
normalizeGitHubUrl,
|
|
parsePorcelain,
|
|
postDiscordStatus,
|
|
summarizeChecks
|
|
} = require('../src/services/repositoryStatusDiscordService');
|
|
|
|
describe('Repository Discord status', () => {
|
|
test('normalizes GitHub remotes without leaking transport details', () => {
|
|
expect(normalizeGitHubUrl('[email protected]:alexpolo1/tilbudgivern.git'))
|
|
.toBe('https://github.com/alexpolo1/tilbudgivern');
|
|
expect(normalizeGitHubUrl('https://github.com/alexpolo1/tilbudgivern.git'))
|
|
.toBe('https://github.com/alexpolo1/tilbudgivern');
|
|
});
|
|
|
|
test('counts staged, modified and untracked files', () => {
|
|
expect(parsePorcelain('M staged.js\n M modified.js\n?? new.js\nMM both.js\n')).toEqual({
|
|
changed: 4,
|
|
staged: 2,
|
|
modified: 2,
|
|
untracked: 1
|
|
});
|
|
});
|
|
|
|
test('summarizes pull request checks', () => {
|
|
expect(summarizeChecks([{ statusCheckRollup: [
|
|
{ status: 'COMPLETED', conclusion: 'SUCCESS' },
|
|
{ status: 'IN_PROGRESS', conclusion: '' },
|
|
{ status: 'COMPLETED', conclusion: 'FAILURE' }
|
|
] }])).toEqual({ passed: 1, pending: 1, failed: 1 });
|
|
});
|
|
|
|
test('builds a red, mention-safe Discord card when drift needs action', () => {
|
|
const payload = buildDiscordPayload({
|
|
repositoryUrl: 'https://github.com/alexpolo1/tilbudgivern',
|
|
branch: 'main',
|
|
upstream: 'origin/main',
|
|
ahead: 0,
|
|
behind: 1,
|
|
worktree: { changed: 0, staged: 0, modified: 0, untracked: 0 },
|
|
latestCommit: { hash: 'abc1234', subject: 'fix: test' },
|
|
pullRequests: [],
|
|
issues: [],
|
|
checks: { passed: 1, pending: 0, failed: 1 },
|
|
health: { ok: false, label: 'Utilgængelig' }
|
|
}, new Date('2026-08-18T06:30:00.000Z'));
|
|
|
|
expect(payload.allowed_mentions).toEqual({ parse: [] });
|
|
expect(payload.embeds[0]).toMatchObject({ color: 15548997 });
|
|
expect(payload.embeds[0].title).toContain('Kræver handling');
|
|
expect(JSON.stringify(payload)).not.toContain('webhook');
|
|
});
|
|
|
|
test('fits hostile repository text inside Discord webhook embed limits', () => {
|
|
const huge = 'x'.repeat(5000);
|
|
const payload = buildDiscordPayload({
|
|
repositoryUrl: 'https://github.com/alexpolo1/tilbudgivern',
|
|
branch: huge,
|
|
upstream: huge,
|
|
ahead: 999,
|
|
behind: 999,
|
|
worktree: { changed: 999, staged: 999, modified: 999, untracked: 999 },
|
|
latestCommit: { hash: huge, subject: huge },
|
|
pullRequests: Array.from({ length: 20 }, (_, index) => ({ number: index + 1, url: 'https://example.com', title: huge, statusCheckRollup: [] })),
|
|
issues: Array.from({ length: 20 }, (_, index) => ({ number: index + 1, url: 'https://example.com', title: huge })),
|
|
checks: { passed: 999, pending: 999, failed: 999 },
|
|
health: { ok: false, label: huge }
|
|
});
|
|
|
|
expect(discordPayloadViolations(payload)).toEqual([]);
|
|
const embed = payload.embeds[0];
|
|
const aggregate = (embed.title || '').length + (embed.description || '').length
|
|
+ (embed.footer?.text || '').length
|
|
+ embed.fields.reduce((sum, field) => sum + field.name.length + field.value.length, 0);
|
|
expect(aggregate).toBeLessThanOrEqual(6000);
|
|
});
|
|
|
|
test('refuses to post a payload that exceeds Discord bot limits', async () => {
|
|
const fetchImpl = jest.fn();
|
|
await expect(postDiscordStatus({
|
|
webhookUrl: 'https://discord.example/webhook-id/token',
|
|
payload: { content: 'x'.repeat(2001), embeds: [] },
|
|
fetchImpl
|
|
})).rejects.toThrow(/Discord payload exceeds limits/);
|
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('posts JSON with wait enabled without embedding the webhook in payload', async () => {
|
|
const fetchImpl = jest.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ id: 'message-1' })
|
|
});
|
|
const payload = { allowed_mentions: { parse: [] }, embeds: [] };
|
|
|
|
await expect(postDiscordStatus({
|
|
webhookUrl: 'https://discord.example/webhook-id/token',
|
|
payload,
|
|
fetchImpl
|
|
})).resolves.toEqual({ id: 'message-1' });
|
|
|
|
expect(fetchImpl.mock.calls[0][0].searchParams.get('wait')).toBe('true');
|
|
expect(JSON.parse(fetchImpl.mock.calls[0][1].body)).toEqual(payload);
|
|
});
|
|
});
|