Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
559 lines
20 KiB
TypeScript
559 lines
20 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import {
|
|
normalizeInboundDomain,
|
|
validateClaimableDomain,
|
|
mapResendDomainStatus,
|
|
resolveClaimedDomainStatus,
|
|
claimCustomDomain,
|
|
checkCustomDomainVerification,
|
|
removeCustomDomain,
|
|
findCompanyForRecipientDomains,
|
|
applyDomainStatusFromWebhook,
|
|
} from '@/extensions/general/invoice-inbox/lib/custom-domains'
|
|
import { createQueuedMockSupabase } from '@/tests/helpers'
|
|
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
|
|
const { domainsMock } = vi.hoisted(() => ({
|
|
domainsMock: {
|
|
create: vi.fn(),
|
|
get: vi.fn(),
|
|
verify: vi.fn(),
|
|
remove: vi.fn(),
|
|
list: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
vi.mock('resend', () => ({
|
|
Resend: class {
|
|
domains = domainsMock
|
|
},
|
|
}))
|
|
|
|
const RECEIVING_RECORD = {
|
|
record: 'Receiving',
|
|
name: 'hansbolag.example',
|
|
value: 'inbound.resend.example',
|
|
type: 'MX',
|
|
ttl: 'Auto',
|
|
status: 'not_started',
|
|
priority: 10,
|
|
}
|
|
|
|
describe('normalizeInboundDomain', () => {
|
|
it('lowercases and strips trailing dots', () => {
|
|
expect(normalizeInboundDomain('Faktura.HansBolag.SE.')).toBe('faktura.hansbolag.se')
|
|
})
|
|
|
|
it('accepts a pasted URL', () => {
|
|
expect(normalizeInboundDomain('https://hansbolag.se/kontakt?x=1')).toBe('hansbolag.se')
|
|
})
|
|
|
|
it('accepts a pasted email address', () => {
|
|
expect(normalizeInboundDomain('faktura@hansbolag.se')).toBe('hansbolag.se')
|
|
})
|
|
|
|
it('punycodes Swedish IDN domains', () => {
|
|
const result = normalizeInboundDomain('blåbär.se')
|
|
expect(result).not.toBeNull()
|
|
expect(result!.startsWith('xn--')).toBe(true)
|
|
expect(result!.endsWith('.se')).toBe(true)
|
|
})
|
|
|
|
it('rejects hostnames without a dot', () => {
|
|
expect(normalizeInboundDomain('nodots')).toBeNull()
|
|
})
|
|
|
|
it('rejects empty input', () => {
|
|
expect(normalizeInboundDomain('')).toBeNull()
|
|
expect(normalizeInboundDomain(' ')).toBeNull()
|
|
})
|
|
|
|
it('rejects IP addresses', () => {
|
|
expect(normalizeInboundDomain('192.168.0.1')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('validateClaimableDomain', () => {
|
|
beforeEach(() => {
|
|
process.env.RESEND_INBOUND_DOMAIN = 'arcim.example'
|
|
process.env.NEXT_PUBLIC_APP_URL = 'https://app.gnubok.example'
|
|
})
|
|
|
|
it('blocks public mailbox providers', () => {
|
|
expect(validateClaimableDomain('gmail.com')).not.toBeNull()
|
|
expect(validateClaimableDomain('outlook.com')).not.toBeNull()
|
|
})
|
|
|
|
it('blocks the shared inbound domain and its subdomains', () => {
|
|
expect(validateClaimableDomain('arcim.example')).not.toBeNull()
|
|
expect(validateClaimableDomain('foo.arcim.example')).not.toBeNull()
|
|
})
|
|
|
|
it('blocks the app domain', () => {
|
|
expect(validateClaimableDomain('app.gnubok.example')).not.toBeNull()
|
|
})
|
|
|
|
it('allows a normal customer domain', () => {
|
|
expect(validateClaimableDomain('hansbolag.se')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('mapResendDomainStatus', () => {
|
|
it('maps Resend statuses to our three buckets', () => {
|
|
expect(mapResendDomainStatus('verified')).toBe('verified')
|
|
// temporary_failure = previously verified, Resend still routing: keep routing.
|
|
expect(mapResendDomainStatus('temporary_failure')).toBe('verified')
|
|
expect(mapResendDomainStatus('failed')).toBe('failed')
|
|
expect(mapResendDomainStatus('pending')).toBe('pending')
|
|
expect(mapResendDomainStatus('not_started')).toBe('pending')
|
|
})
|
|
})
|
|
|
|
describe('resolveClaimedDomainStatus', () => {
|
|
it('forces an adopted orphan to pending even when Resend reports verified', () => {
|
|
// The security-critical case: adopting a domain freed by a deleted company
|
|
// must NOT inherit a stale 'verified': DNS control has to be re-proven.
|
|
expect(resolveClaimedDomainStatus(true, 'verified')).toBe('pending')
|
|
expect(resolveClaimedDomainStatus(true, 'temporary_failure')).toBe('pending')
|
|
})
|
|
|
|
it('maps Resend status normally for a freshly created (non-adopted) domain', () => {
|
|
expect(resolveClaimedDomainStatus(false, 'pending')).toBe('pending')
|
|
expect(resolveClaimedDomainStatus(false, 'not_started')).toBe('pending')
|
|
expect(resolveClaimedDomainStatus(false, 'verified')).toBe('verified')
|
|
})
|
|
})
|
|
|
|
describe('claimCustomDomain', () => {
|
|
const originalEnv = { ...process.env }
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.RESEND_API_KEY = 'test-key'
|
|
process.env.RESEND_INBOUND_DOMAIN = 'arcim.example'
|
|
process.env.NEXT_PUBLIC_APP_URL = 'https://app.gnubok.example'
|
|
})
|
|
|
|
afterEach(() => {
|
|
process.env = { ...originalEnv }
|
|
})
|
|
|
|
it('rejects an unparseable domain without touching the database', async () => {
|
|
const { supabase } = createQueuedMockSupabase()
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'not a domain!')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) expect(result.status).toBe(400)
|
|
expect(domainsMock.create).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects blocked domains', async () => {
|
|
const { supabase } = createQueuedMockSupabase()
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'gmail.com')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) expect(result.status).toBe(400)
|
|
})
|
|
|
|
it('registers the domain in Resend with receiving-only capabilities and stores the records', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', domain: 'hansbolag.example' } }) // insert
|
|
enqueue({
|
|
data: {
|
|
id: 'row-1',
|
|
company_id: 'company-1',
|
|
domain: 'hansbolag.example',
|
|
status: 'pending',
|
|
resend_domain_id: 'rd_1',
|
|
dns_records: [RECEIVING_RECORD],
|
|
},
|
|
}) // update
|
|
domainsMock.create.mockResolvedValue({ data: { id: 'rd_1' }, error: null })
|
|
domainsMock.get.mockResolvedValue({
|
|
data: { id: 'rd_1', status: 'pending', records: [RECEIVING_RECORD] },
|
|
error: null,
|
|
})
|
|
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'HansBolag.example')
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) {
|
|
expect(result.data.resend_domain_id).toBe('rd_1')
|
|
expect(result.data.status).toBe('pending')
|
|
}
|
|
expect(domainsMock.create).toHaveBeenCalledWith({
|
|
name: 'hansbolag.example',
|
|
region: 'eu-west-1',
|
|
capabilities: { receiving: 'enabled', sending: 'disabled' },
|
|
})
|
|
})
|
|
|
|
it('returns 409 when the company already has a domain', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({
|
|
data: null,
|
|
error: { code: '23505', message: 'duplicate key value violates unique constraint "idx_company_inbound_domains_company"' },
|
|
})
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'hansbolag.example')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) {
|
|
expect(result.status).toBe(409)
|
|
expect(result.error).toContain('redan en egen domän')
|
|
}
|
|
expect(domainsMock.create).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('returns 409 when another company owns the domain, without leaking who', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({
|
|
data: null,
|
|
error: { code: '23505', message: 'duplicate key value violates unique constraint "idx_company_inbound_domains_domain"' },
|
|
})
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'hansbolag.example')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) {
|
|
expect(result.status).toBe(409)
|
|
expect(result.error).toBe('Domänen är redan registrerad.')
|
|
}
|
|
})
|
|
|
|
it('rolls back the row when Resend registration fails and no existing domain can be adopted', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', domain: 'hansbolag.example' } }) // insert
|
|
enqueue({ data: null }) // rollback delete
|
|
domainsMock.create.mockResolvedValue({
|
|
data: null,
|
|
error: { message: 'quota exceeded', statusCode: 422, name: 'validation_error' },
|
|
})
|
|
domainsMock.list.mockResolvedValue({ data: { data: [], has_more: false }, error: null })
|
|
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'hansbolag.example')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) {
|
|
expect(result.status).toBe(502)
|
|
expect(result.error).toContain('quota exceeded')
|
|
}
|
|
})
|
|
|
|
it('adopts an orphaned receiving-only Resend domain when create says it already exists', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', domain: 'hansbolag.example' } }) // insert
|
|
enqueue({
|
|
data: { id: 'row-1', resend_domain_id: 'rd_9', status: 'pending', domain: 'hansbolag.example' },
|
|
}) // update
|
|
domainsMock.create.mockResolvedValue({
|
|
data: null,
|
|
error: { message: 'domain already exists', statusCode: 409, name: 'validation_error' },
|
|
})
|
|
domainsMock.list.mockResolvedValue({
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 'rd_9',
|
|
name: 'HansBolag.example',
|
|
status: 'pending',
|
|
capabilities: { receiving: 'enabled', sending: 'disabled' },
|
|
},
|
|
],
|
|
has_more: false,
|
|
},
|
|
error: null,
|
|
})
|
|
domainsMock.get.mockResolvedValue({
|
|
data: { id: 'rd_9', status: 'pending', records: [RECEIVING_RECORD] },
|
|
error: null,
|
|
})
|
|
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'hansbolag.example')
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) expect(result.data.resend_domain_id).toBe('rd_9')
|
|
})
|
|
|
|
it('refuses to adopt a sending domain: the platform outbound domain must never bind to a tenant', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', domain: 'hansbolag.example' } }) // insert
|
|
enqueue({ data: null }) // rollback delete
|
|
domainsMock.create.mockResolvedValue({
|
|
data: null,
|
|
error: { message: 'domain already exists', statusCode: 409, name: 'validation_error' },
|
|
})
|
|
domainsMock.list.mockResolvedValue({
|
|
data: {
|
|
data: [
|
|
{
|
|
id: 'rd_prod_send',
|
|
name: 'hansbolag.example',
|
|
status: 'verified',
|
|
capabilities: { receiving: 'disabled', sending: 'enabled' },
|
|
},
|
|
],
|
|
has_more: false,
|
|
},
|
|
error: null,
|
|
})
|
|
|
|
const result = await claimCustomDomain(supabase as unknown as SupabaseClient, 'company-1', 'hansbolag.example')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) {
|
|
expect(result.status).toBe(409)
|
|
expect(result.error).toContain('underdomän')
|
|
}
|
|
expect(domainsMock.get).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('checkCustomDomainVerification', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.RESEND_API_KEY = 'test-key'
|
|
})
|
|
|
|
it('returns 404 when the company has no custom domain', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: null })
|
|
const result = await checkCustomDomainVerification(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) expect(result.status).toBe(404)
|
|
})
|
|
|
|
it('persists the verified status from Resend', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({
|
|
data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_1', verified_at: null },
|
|
}) // select
|
|
enqueue({
|
|
data: { id: 'row-1', status: 'verified', domain: 'hansbolag.example' },
|
|
}) // update
|
|
domainsMock.verify.mockResolvedValue({ data: { object: 'domain', id: 'rd_1' }, error: null })
|
|
domainsMock.get.mockResolvedValue({
|
|
data: {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
capabilities: { receiving: 'enabled', sending: 'disabled' },
|
|
records: [{ ...RECEIVING_RECORD, status: 'verified' }],
|
|
},
|
|
error: null,
|
|
})
|
|
|
|
const result = await checkCustomDomainVerification(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(true)
|
|
if (result.ok) expect(result.data.status).toBe('verified')
|
|
expect(domainsMock.verify).toHaveBeenCalledWith('rd_1')
|
|
})
|
|
|
|
it('never verifies a domain whose receiving capability is disabled', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({
|
|
data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_send', verified_at: null },
|
|
}) // select: no update should follow
|
|
domainsMock.verify.mockResolvedValue({ data: { object: 'domain', id: 'rd_send' }, error: null })
|
|
domainsMock.get.mockResolvedValue({
|
|
data: {
|
|
id: 'rd_send',
|
|
status: 'verified', // verified for SENDING: must not count
|
|
capabilities: { receiving: 'disabled', sending: 'enabled' },
|
|
records: [],
|
|
},
|
|
error: null,
|
|
})
|
|
|
|
const result = await checkCustomDomainVerification(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) {
|
|
expect(result.status).toBe(409)
|
|
expect(result.error).toContain('mottagning')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('removeCustomDomain', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.RESEND_API_KEY = 'test-key'
|
|
})
|
|
|
|
const RECEIVING_ONLY_DOMAIN = {
|
|
id: 'rd_1',
|
|
status: 'pending',
|
|
capabilities: { receiving: 'enabled', sending: 'disabled' },
|
|
records: [],
|
|
}
|
|
|
|
it('removes the Resend domain and deletes the row', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_1' } }) // select
|
|
enqueue({ data: null }) // delete
|
|
domainsMock.get.mockResolvedValue({ data: RECEIVING_ONLY_DOMAIN, error: null })
|
|
domainsMock.remove.mockResolvedValue({ data: { id: 'rd_1', deleted: true }, error: null })
|
|
|
|
const result = await removeCustomDomain(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(true)
|
|
expect(domainsMock.remove).toHaveBeenCalledWith('rd_1')
|
|
})
|
|
|
|
it('tolerates a domain already gone from Resend', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_1' } })
|
|
enqueue({ data: null })
|
|
domainsMock.get.mockResolvedValue({
|
|
data: null,
|
|
error: { message: 'not found', statusCode: 404, name: 'not_found' },
|
|
})
|
|
|
|
const result = await removeCustomDomain(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(true)
|
|
expect(domainsMock.remove).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('keeps the row when Resend removal fails: a verified orphan must never become adoptable', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_1' } })
|
|
domainsMock.get.mockResolvedValue({ data: RECEIVING_ONLY_DOMAIN, error: null })
|
|
domainsMock.remove.mockResolvedValue({
|
|
data: null,
|
|
error: { message: 'internal error', statusCode: 500, name: 'application_error' },
|
|
})
|
|
|
|
const result = await removeCustomDomain(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(false)
|
|
if (!result.ok) expect(result.status).toBe(502)
|
|
})
|
|
|
|
it('deletes the row but never the Resend domain when it is not receiving-only', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
// Legacy row bound to the platform's outbound sending domain.
|
|
enqueue({ data: { id: 'row-1', company_id: 'company-1', resend_domain_id: 'rd_prod_send' } })
|
|
enqueue({ data: null }) // row delete
|
|
domainsMock.get.mockResolvedValue({
|
|
data: {
|
|
id: 'rd_prod_send',
|
|
status: 'verified',
|
|
capabilities: { receiving: 'disabled', sending: 'enabled' },
|
|
records: [],
|
|
},
|
|
error: null,
|
|
})
|
|
|
|
const result = await removeCustomDomain(supabase as unknown as SupabaseClient, 'company-1')
|
|
expect(result.ok).toBe(true)
|
|
expect(domainsMock.remove).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('findCompanyForRecipientDomains', () => {
|
|
it('returns the first match in recipient order regardless of row order', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({
|
|
data: [
|
|
{ company_id: 'c2', domain: 'second.example' },
|
|
{ company_id: 'c1', domain: 'first.example' },
|
|
],
|
|
})
|
|
const match = await findCompanyForRecipientDomains(
|
|
supabase as unknown as SupabaseClient,
|
|
['first.example', 'second.example'],
|
|
)
|
|
expect(match).toEqual({ companyId: 'c1', domain: 'first.example' })
|
|
})
|
|
|
|
it('returns null when nothing matches', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: [] })
|
|
const match = await findCompanyForRecipientDomains(
|
|
supabase as unknown as SupabaseClient,
|
|
['unknown.example'],
|
|
)
|
|
expect(match).toBeNull()
|
|
})
|
|
|
|
it('returns null without querying when there are no recipient domains', async () => {
|
|
const { supabase } = createQueuedMockSupabase()
|
|
const match = await findCompanyForRecipientDomains(supabase as unknown as SupabaseClient, [])
|
|
expect(match).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('applyDomainStatusFromWebhook', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.RESEND_API_KEY = 'test-key'
|
|
})
|
|
|
|
it('updates the matching row and reports true when receiving is confirmed', async () => {
|
|
domainsMock.get.mockResolvedValue({
|
|
data: {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
capabilities: { receiving: 'enabled', sending: 'disabled' },
|
|
records: [RECEIVING_RECORD],
|
|
},
|
|
error: null,
|
|
})
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', verified_at: null } })
|
|
enqueue({ data: null }) // update
|
|
const matched = await applyDomainStatusFromWebhook(supabase as unknown as SupabaseClient, {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
records: [],
|
|
})
|
|
expect(matched).toBe(true)
|
|
expect(domainsMock.get).toHaveBeenCalledWith('rd_1')
|
|
})
|
|
|
|
it('never flips a sending-only domain to verified off the event status', async () => {
|
|
domainsMock.get.mockResolvedValue({
|
|
data: {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
capabilities: { receiving: 'disabled', sending: 'enabled' },
|
|
records: [],
|
|
},
|
|
error: null,
|
|
})
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', verified_at: null } })
|
|
enqueue({ data: null }) // update (last_checked_at only)
|
|
const matched = await applyDomainStatusFromWebhook(supabase as unknown as SupabaseClient, {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
records: [],
|
|
})
|
|
// Row matched, but the verified transition was refused.
|
|
expect(matched).toBe(true)
|
|
expect(domainsMock.get).toHaveBeenCalledWith('rd_1')
|
|
})
|
|
|
|
it('keeps stored status when the capability lookup fails', async () => {
|
|
domainsMock.get.mockResolvedValue({ data: null, error: { message: 'boom' } })
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', verified_at: null } })
|
|
enqueue({ data: null }) // update (last_checked_at only)
|
|
const matched = await applyDomainStatusFromWebhook(supabase as unknown as SupabaseClient, {
|
|
id: 'rd_1',
|
|
status: 'verified',
|
|
})
|
|
expect(matched).toBe(true)
|
|
})
|
|
|
|
it('skips the capability lookup for non-verified statuses', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: { id: 'row-1', verified_at: null } })
|
|
enqueue({ data: null }) // update
|
|
const matched = await applyDomainStatusFromWebhook(supabase as unknown as SupabaseClient, {
|
|
id: 'rd_1',
|
|
status: 'failed',
|
|
})
|
|
expect(matched).toBe(true)
|
|
expect(domainsMock.get).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('reports false for unknown Resend domain ids', async () => {
|
|
const { supabase, enqueue } = createQueuedMockSupabase()
|
|
enqueue({ data: null })
|
|
const matched = await applyDomainStatusFromWebhook(supabase as unknown as SupabaseClient, {
|
|
id: 'rd_unknown',
|
|
status: 'verified',
|
|
})
|
|
expect(matched).toBe(false)
|
|
expect(domainsMock.get).not.toHaveBeenCalled()
|
|
})
|
|
})
|