import assert from 'node:assert/strict'; import test from 'node:test'; import { createWorkspace, deleteWorkspaceIfEmpty, ensurePersonalWorkspaceForUser, findAlternateWorkspaceForUser, getPlatformAdminSummary, listOwnedWorkspacesByOwnerEmail, updateWorkspace, } from './workspaceRepository.js'; import { mockDb } from '../test/mockDb.js'; import type { UserRow } from '../types.js'; const user: UserRow = { id: 'user-1', email: 'owner@example.com', password_hash: null, name: 'Owner', created_at: '2026-04-14T00:00:00.000Z', }; test('ensurePersonalWorkspaceForUser returns an existing workspace without creating one', async () => { const { calls } = mockDb({ rowCount: 1, rows: [{ workspace_id: 42 }], }); const workspaceId = await ensurePersonalWorkspaceForUser(user); assert.equal(workspaceId, 42); assert.equal(calls.length, 1); assert.match(calls[0].text, /FROM workspace_members/); }); test('createWorkspace inserts owner membership and returns the created workspace', async () => { const { calls } = mockDb( { rowCount: 1, rows: [] }, { rowCount: 1, rows: [] }, { rowCount: 1, rows: [ { id: 9, name: 'My Rescue', workspace_type: 'rescue', billing_email: 'billing@example.com', billing_plan: 'rescue_free', billing_interval: 'monthly', created_at: '2026-04-14T00:00:00.000Z', updated_at: '2026-04-14T00:00:00.000Z', }, ], }, ); const workspace = await createWorkspace({ id: 9, name: 'My Rescue', workspaceType: 'rescue', billingEmail: 'billing@example.com', billingPlan: 'rescue_free', billingInterval: 'monthly', owner: user, }); assert.equal(workspace?.id, 9); assert.equal(calls.length, 3); assert.match(calls[0].text, /INSERT INTO workspaces/); assert.match(calls[1].text, /INSERT INTO workspace_members/); assert.match(calls[2].text, /SELECT id, name, workspace_type/); }); test('updateWorkspace converts an existing household flock to rescue without inserting a new flock', async () => { const { calls } = mockDb({ rowCount: 1, rows: [ { id: 42, name: 'Converted Rescue', workspace_type: 'rescue', billing_email: 'billing@example.com', billing_plan: 'rescue_free', billing_interval: 'monthly', subscription_status: 'active', rescue_verification_status: 'pending', created_at: '2026-04-14T00:00:00.000Z', updated_at: '2026-04-15T00:00:00.000Z', }, ], }); const workspace = await updateWorkspace({ workspaceId: 42, name: 'Converted Rescue', workspaceType: 'rescue', billingEmail: 'billing@example.com', billingPlan: 'rescue_free', billingInterval: 'monthly', }); assert.equal(workspace?.id, 42); assert.equal(workspace?.workspace_type, 'rescue'); assert.equal(calls.length, 1); assert.match(calls[0].text, /UPDATE workspaces/); assert.doesNotMatch(calls[0].text, /INSERT INTO workspaces/); assert.deepEqual(calls[0].params, [42, 'Converted Rescue', 'rescue', 'billing@example.com', 'rescue_free', 'monthly']); }); test('deleteWorkspaceIfEmpty blocks deletion when birds are still assigned', async () => { const { calls } = mockDb( { rowCount: 1, rows: [{ count: '2' }], }, ); const result = await deleteWorkspaceIfEmpty(42); assert.deepEqual(result, { deleted: false, reason: 'birds_present' }); assert.equal(calls.length, 1); assert.match(calls[0].text, /FROM birds/); }); test('deleteWorkspaceIfEmpty deletes an empty workspace', async () => { const { calls } = mockDb( { rowCount: 1, rows: [{ count: '0' }], }, { rowCount: 1, rows: [{ id: 42 }], }, ); const result = await deleteWorkspaceIfEmpty(42); assert.deepEqual(result, { deleted: true }); assert.equal(calls.length, 2); assert.match(calls[1].text, /DELETE FROM workspaces/); }); test('findAlternateWorkspaceForUser returns another workspace when available', async () => { const { calls } = mockDb({ rowCount: 1, rows: [{ workspace_id: 84 }], }); const workspaceId = await findAlternateWorkspaceForUser('user-1', 42); assert.equal(workspaceId, 84); assert.deepEqual(calls[0].params, ['user-1', 42]); }); test('listOwnedWorkspacesByOwnerEmail resolves accepted owner flocks by email', async () => { const { calls } = mockDb({ rowCount: 1, rows: [ { id: 84, name: 'Receiving Flock', workspace_type: 'standard', billing_email: 'receiver@example.com', billing_plan: 'household_basic', subscription_status: 'active', rescue_verification_status: 'not_required', created_at: '2026-04-14T00:00:00.000Z', updated_at: '2026-04-14T00:00:00.000Z', }, ], }); const workspaces = await listOwnedWorkspacesByOwnerEmail('Receiver@Example.com', 42); assert.equal(workspaces[0]?.id, 84); assert.deepEqual(calls[0].params, ['Receiver@Example.com', 42]); assert.match(calls[0].text, /workspace_members\.role = 'owner'/); assert.match(calls[0].text, /accepted_at IS NOT NULL/); assert.match(calls[0].text, /workspaces\.id <> \$2/); }); test('getPlatformAdminSummary counts memorialized birds separately', async () => { const { calls } = mockDb({ rowCount: 1, rows: [ { total_birds: 8, memorialized_birds: 3, total_users: 4, total_workspaces: 2, rescue_workspaces: 1, rescue_birds: 5, pending_rescues: 1, daily_users: 2, }, ], }); const summary = await getPlatformAdminSummary(); assert.equal(summary?.memorialized_birds, 3); assert.equal(calls.length, 1); assert.match(calls[0].text, /memorialized_birds/); assert.match(calls[0].text, /memorialized_at IS NOT NULL/); assert.match(calls[0].text, /rescue_birds/); assert.match(calls[0].text, /workspaces\.workspace_type = 'rescue'/); });