import { nanoid } from 'nanoid' import * as Runtime from '../../../test/runtime.js' import * as Db from '../Db.js' import * as EarlyAccess from './earlyAccess.js' import * as EnabledBillingSources from './enabledBillingSources.js' import * as Invitations from './invitations.js' import * as Organizations from './organizations.js' import * as StripeCustomers from './stripeCustomers.js' import * as Users from './users.js' const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` }) async function createOwned(db: Db.Db, options: createOwned.Options) { const user = await Users.upsertByAddress(db, { address: options.address }) if (options.email) await Users.setEmail(db, user.id, options.email) const organization = await Organizations.createOwned(db, { name: options.name, userId: user.id }) return { organization, user } } declare namespace createOwned { /** Inputs for an owned-organization fixture. */ type Options = { /** User address. */ address: string /** Optional user email. */ email?: string | undefined /** Organization name. */ name: string } } /** Redacts nondeterministic row fields. */ function redact(record: EnabledBillingSources.Record) { return { ...record, createdAt: '' } } describe('add', () => { test('behavior: enables each source once per organization', async () => { const db = create() await db.migrate() const stripe = await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'stripe', }) expect(stripe && redact(stripe)).toMatchInlineSnapshot(` { "createdAt": "", "createdBy": "admin@tempo.xyz", "orgId": "org_1", "source": "stripe", } `) expect( await EnabledBillingSources.add(db, { createdBy: 'other@tempo.xyz', orgId: 'org_1', source: 'stripe', }), ).toBeUndefined() expect( await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'tempo', }), ).toBeDefined() await db.close() }) }) describe('listByOrg', () => { test('behavior: lists only the organization in canonical source order', async () => { const db = create() await db.migrate() await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'tempo', }) await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_2', source: 'stripe', }) await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'stripe', }) expect((await EnabledBillingSources.listByOrg(db, 'org_1')).map(redact)).toMatchInlineSnapshot(` [ { "createdAt": "", "createdBy": "admin@tempo.xyz", "orgId": "org_1", "source": "stripe", }, { "createdAt": "", "createdBy": "admin@tempo.xyz", "orgId": "org_1", "source": "tempo", }, ] `) await db.close() }) test('behavior: new organizations start with no enabled sources', async () => { const db = create() await db.migrate() await Organizations.create(db, { id: 'org_new', name: 'New' }) expect(await EnabledBillingSources.listByOrg(db, 'org_new')).toEqual([]) await db.close() }) }) describe('remove', () => { test('behavior: removes one source and reports absence', async () => { const db = create() await db.migrate() await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'stripe', }) await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'tempo', }) expect( redact( (await EnabledBillingSources.remove(db, { orgId: 'org_1', source: 'stripe', }))!, ), ).toMatchInlineSnapshot(` { "createdAt": "", "createdBy": "admin@tempo.xyz", "orgId": "org_1", "source": "stripe", } `) expect( await EnabledBillingSources.remove(db, { orgId: 'org_1', source: 'stripe' }), ).toBeUndefined() expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'tempo' })).toBe( true, ) await db.close() }) }) describe('isEnabled', () => { test('behavior: checks one organization and source', async () => { const db = create() await db.migrate() await EnabledBillingSources.add(db, { createdBy: 'admin@tempo.xyz', orgId: 'org_1', source: 'stripe', }) expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'stripe' })).toBe( true, ) expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_1', source: 'tempo' })).toBe( false, ) expect(await EnabledBillingSources.isEnabled(db, { orgId: 'org_2', source: 'stripe' })).toBe( false, ) await db.close() }) }) describe('migration', () => { test('behavior: catch-up backfills one Stripe source per distinct customer organization', async () => { const db = create() await db.migrate() await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_live_1' }) await StripeCustomers.create(db, { environment: 'sandbox', orgId: 'org_1', stripeCustomerId: 'cus_test_1', }) await StripeCustomers.create(db, { orgId: 'org_2', stripeCustomerId: 'cus_live_2' }) await db.kysely .deleteFrom('migrations') .where('name', '=', '0072_enabled_billing_sources_stripe_catch_up') .execute() await db.migrate() await db.kysely .deleteFrom('migrations') .where('name', '=', '0072_enabled_billing_sources_stripe_catch_up') .execute() await db.migrate() expect( (await EnabledBillingSources.listByOrg(db, 'org_1')).map((record) => ({ createdBy: record.createdBy, source: record.source, })), ).toMatchInlineSnapshot(` [ { "createdBy": "migration", "source": "stripe", }, ] `) expect((await EnabledBillingSources.listByOrg(db, 'org_2')).map((record) => record.source)) .toMatchInlineSnapshot(` [ "stripe", ] `) await db.close() }) test('behavior: backfills Stripe only for organizations owned by allowlisted users', async () => { const db = create() await db.migrate() await EarlyAccess.add(db, { createdBy: 'admin@tempo.xyz', entry: 'exact@example.net' }) await EarlyAccess.add(db, { createdBy: 'admin@tempo.xyz', entry: 'example.org' }) const exact = await createOwned(db, { address: `0x${'11'.repeat(20)}`, email: ' Exact@Example.NET ', name: 'Exact', }) const domain = await createOwned(db, { address: `0x${'22'.repeat(20)}`, email: 'Owner@Example.ORG', name: 'Domain', }) const invited = await createOwned(db, { address: `0x${'33'.repeat(20)}`, email: 'Invited@Example.NET', name: 'Invited', }) const legacyInvitation = await Invitations.create(db, { email: 'invited@example.net', expiresAt: '2020-01-01T00:00:00.000Z', invitedBy: exact.user.id, orgId: exact.organization.id, role: 'member', }) await db.kysely .updateTable('invitations') .set({ grantsEarlyAccess: true }) .where('id', '=', legacyInvitation.id) .execute() const plain = await createOwned(db, { address: `0x${'44'.repeat(20)}`, email: 'plain@example.net', name: 'Plain', }) const emailLess = await createOwned(db, { address: `0x${'55'.repeat(20)}`, name: 'Email-less', }) for (let attempt = 0; attempt < 2; attempt++) { await db.kysely .deleteFrom('migrations') .where('name', '=', '0073_enabled_billing_sources_early_access_backfill') .execute() await db.migrate() } const result = await Promise.all( [exact, domain, invited, plain, emailLess].map(async ({ organization }) => (await EnabledBillingSources.listByOrg(db, organization.id)).map( ({ createdBy, source }) => ({ createdBy, source }), ), ), ) expect(result).toMatchInlineSnapshot(` [ [ { "createdBy": "migration", "source": "stripe", }, ], [ { "createdBy": "migration", "source": "stripe", }, ], [], [], [], ] `) await db.close() }) })