import { AdminPublicError, adminErrorResponse, adminRpcError, parsePagination, parseRoleChangeRequest, parseSubscriptionMutationRequest, readAdminJson, sanitizeAuditRecord, validateQueryKeys, validateRoleRpcResult, validateSubscriptionRpcResult, } from './admin-contract.ts' import { corsHeaders } from './cors.ts' const OPERATION_ID = '11111111-2222-4333-8444-555555555555' const USER_ID = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee' function assert(condition: boolean, message: string): asserts condition { if (!condition) throw new Error(message) } function expectAdminError(action: () => unknown, code: string, status: number): void { let received: unknown try { action() } catch (error) { received = error } assert(received instanceof AdminPublicError, `expected AdminPublicError for ${code}`) assert(received.code === code, `expected ${code}, received ${received.code}`) assert(received.status === status, `expected ${status}, received ${received.status}`) } Deno.test('admin role mutation requires strict UUID, fields, role and audit memo', () => { const parsed = parseRoleChangeRequest({ userId: USER_ID, newRole: 'manager', memo: ' verified support assignment ', }, OPERATION_ID) assert(parsed.userId === USER_ID, 'target UUID must be preserved') assert(parsed.newRole === 'manager', 'managed role must be preserved') assert(parsed.memo === 'verified support assignment', 'memo must be normalized') expectAdminError( () => parseRoleChangeRequest({ userId: USER_ID, newRole: 'owner', memo: 'valid reason' }, OPERATION_ID), 'invalid_newRole', 400, ) expectAdminError( () => parseRoleChangeRequest({ userId: USER_ID, newRole: 'manager', memo: 'ok', rawSql: 'select 1' }, OPERATION_ID), 'unexpected_request_field', 400, ) expectAdminError( () => parseRoleChangeRequest({ userId: USER_ID, newRole: 'manager', memo: 'valid reason' }, 'not-a-uuid'), 'valid_idempotency_key_required', 400, ) }) Deno.test('subscription mutations enforce action-specific fields and numeric/date bounds', () => { const created = parseSubscriptionMutationRequest('create', { userId: USER_ID, tier: 'pro_plus', status: 'active', currentPeriodEnd: '2027-08-21T12:00:00+09:00', overageCredits: 100, adminNote: 'VIP recovery', memo: 'verified account recovery', }, OPERATION_ID) assert(created.currentPeriodEnd === '2027-08-21T03:00:00.000Z', 'period end must normalize to UTC') assert(created.overageCredits === 100, 'valid bounded credits must pass') const deleted = parseSubscriptionMutationRequest( 'delete', { memo: 'verified subscription removal' }, OPERATION_ID, USER_ID, ) assert(deleted.action === 'delete' && deleted.userId === USER_ID, 'delete target must come from query') expectAdminError( () => parseSubscriptionMutationRequest('create', { userId: USER_ID, memo: 'valid reason' }, OPERATION_ID), 'tier_required', 400, ) expectAdminError( () => parseSubscriptionMutationRequest('update', { memo: 'valid reason', overageCredits: 1_000_001 }, OPERATION_ID, USER_ID), 'invalid_overageCredits', 400, ) expectAdminError( () => parseSubscriptionMutationRequest('delete', { memo: 'valid reason', hardDelete: true }, OPERATION_ID, USER_ID), 'unexpected_request_field', 400, ) }) Deno.test('admin query parsing rejects NaN, unbounded pages and unknown parameters', () => { const valid = new URL('https://example.test/admin-users?page=2&limit=100') validateQueryKeys(valid, ['page', 'limit']) const page = parsePagination(valid) assert(page.from === 100 && page.to === 199, 'bounded pagination range must be exact') expectAdminError( () => parsePagination(new URL('https://example.test/admin-users?page=NaN&limit=20')), 'invalid_pagination', 400, ) expectAdminError( () => parsePagination(new URL('https://example.test/admin-users?page=1&limit=101')), 'invalid_pagination', 400, ) expectAdminError( () => validateQueryKeys(new URL('https://example.test/admin-users?debug=true'), ['page']), 'unexpected_query_parameter', 400, ) }) Deno.test('admin RPC errors and responses expose only stable public contracts', async () => { const unknown = adminRpcError({ code: 'XX000', message: 'password=secret; private SQL stack' }) assert(unknown.status === 500 && unknown.code === 'admin_operation_failed', 'unknown RPC detail must be sanitized') const conflict = adminRpcError({ code: '22023', message: 'idempotency_key_reused_with_different_request' }) assert(conflict.status === 409 && conflict.code === 'idempotency_conflict', 'idempotency conflict must be stable') const response = adminErrorResponse(new Error('database host and secret'), corsHeaders) assert(response.status === 500, 'unexpected error must be a server failure') assert(await response.text() === '{"error":"internal_error"}', 'raw exception text must never reach clients') const roleRequest = parseRoleChangeRequest({ userId: USER_ID, newRole: 'manager', memo: 'valid reason' }, OPERATION_ID) const roleResult = validateRoleRpcResult({ success: true, userId: USER_ID, newRole: 'manager' }, roleRequest) assert(roleResult.success === true, 'valid role RPC result must pass') expectAdminError( () => validateRoleRpcResult({ success: true, userId: USER_ID, newRole: 'admin' }, roleRequest), 'invalid_admin_rpc_response', 502, ) const subscriptionRequest = parseSubscriptionMutationRequest( 'update', { tier: 'pro', memo: 'valid reason' }, OPERATION_ID, USER_ID, ) const subscriptionResult = validateSubscriptionRpcResult({ success: true, subscription: { user_id: USER_ID, tier: 'pro', status: 'active' }, }, subscriptionRequest) assert(subscriptionResult.success === true, 'valid subscription RPC result must pass') }) Deno.test('audit response redacts provider identifiers and secrets recursively', () => { const sanitized = sanitizeAuditRecord({ id: 7, before_data: { tier: 'pro', payple_payer_id: 'payer-secret', nested: { provider_resource_id: 'resource-secret', status: 'active' }, }, after_data: { api_key: 'api-secret', provider: 'payple', payload: { card: 'raw-provider-payload' }, }, }) const encoded = JSON.stringify(sanitized) assert(!encoded.includes('payer-secret'), 'Payple payer id must be removed') assert(!encoded.includes('resource-secret'), 'provider resource id must be removed') assert(!encoded.includes('api-secret'), 'API key must be removed') assert(!encoded.includes('raw-provider-payload'), 'raw provider payload must be removed') assert(encoded.includes('"tier":"pro"') && encoded.includes('"provider":"payple"'), 'safe audit fields must remain') }) Deno.test('admin JSON reader enforces content type, object shape and byte limit', async () => { const valid = await readAdminJson(new Request('https://example.test/admin-users', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ memo: 'valid reason' }), })) assert(valid.memo === 'valid reason', 'valid JSON object must be returned') let received: unknown try { await readAdminJson(new Request('https://example.test/admin-users', { method: 'PATCH', headers: { 'Content-Type': 'text/plain' }, body: '{}', })) } catch (error) { received = error } assert(received instanceof AdminPublicError && received.status === 415, 'non-JSON request must fail closed') }) Deno.test('admin Edge handlers use atomic RPC writes and contain no legacy direct/provider fallback', async () => { const [users, subscriptions, payments, audit] = await Promise.all([ Deno.readTextFile(new URL('../admin-users/index.ts', import.meta.url)), Deno.readTextFile(new URL('../admin-subscriptions/index.ts', import.meta.url)), Deno.readTextFile(new URL('../admin-payments/index.ts', import.meta.url)), Deno.readTextFile(new URL('../admin-audit-log/index.ts', import.meta.url)), ]) assert(payments.includes('overage_credits, admin_note'), 'payment history subscription must include the strict mobile subscription fields') assert(users.includes("rpc('admin_change_user_role_v1'"), 'role mutation must use atomic RPC') assert(subscriptions.includes("rpc('admin_mutate_subscription_v1'"), 'subscription mutation must use atomic RPC') for (const [name, source] of [['users', users], ['subscriptions', subscriptions]] as const) { assert(!source.includes(".update({"), `${name} must not directly update tables`) assert(!source.includes(".insert("), `${name} must not directly insert tables`) assert(!source.includes('writeAuditLog'), `${name} audit must be inside the RPC transaction`) assert(!source.includes('auth.admin.updateUserById'), `${name} must not perform a separate auth mutation`) } assert(payments.includes('payple_live_history_not_configured'), 'live Payple history must fail closed') assert(!payments.includes('paypleAuth'), 'payment reads must not fetch Payple directly') assert(!payments.includes('paypleHistory'), 'raw provider payload must not be returned') for (const source of [users, subscriptions, payments, audit]) { assert(source.includes('verifyCurrentAdminActor'), 'every admin read must re-check the current database role') assert(!source.includes('error.message'), 'raw database errors must not be serialized') assert(!source.includes('JSON.stringify({ error: message })'), 'raw exceptions must not be serialized') } })