chore: consolidate new foundation and archive v1 (#1495)
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createPgliteDb } from './client-pglite.js';
|
||||
import { runPgliteMigrations } from './migrate.js';
|
||||
import type { DbHandle } from './client.js';
|
||||
|
||||
interface PgliteExec {
|
||||
exec(query: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
describe('runPgliteMigrations', () => {
|
||||
let dataDir: string;
|
||||
let handle: DbHandle;
|
||||
|
||||
beforeEach(() => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'mosaic-db-migrate-test-'));
|
||||
handle = createPgliteDb(dataDir);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await handle.close();
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('creates the BetterAuth tables required by the gateway', async () => {
|
||||
await runPgliteMigrations(handle);
|
||||
|
||||
const result = (await handle.db.execute(sql`
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`)) as unknown as { rows: Array<{ table_name: string }> };
|
||||
|
||||
const tables = result.rows.map((r) => r.table_name);
|
||||
|
||||
// Auth tables — required for sign-in / bootstrap to function.
|
||||
expect(tables).toContain('users');
|
||||
expect(tables).toContain('sessions');
|
||||
expect(tables).toContain('accounts');
|
||||
expect(tables).toContain('verifications');
|
||||
|
||||
// Schema sanity check — admin token table consumed by mosaic gateway config.
|
||||
expect(tables).toContain('admin_tokens');
|
||||
});
|
||||
|
||||
it('is idempotent — running twice does not error', async () => {
|
||||
await runPgliteMigrations(handle);
|
||||
await expect(runPgliteMigrations(handle)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('gives accounts an issuer column (#1395) — better-auth >=1.7 requires it', async () => {
|
||||
await runPgliteMigrations(handle);
|
||||
|
||||
const result = (await handle.db.execute(sql`
|
||||
SELECT column_name, is_nullable, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'accounts' AND column_name = 'issuer'
|
||||
`)) as unknown as {
|
||||
rows: Array<{ column_name: string; is_nullable: string; data_type: string }>;
|
||||
};
|
||||
|
||||
// Nullable by design: the 1.5.x line this repo's lockfile resolves to does
|
||||
// not write the field; 1.7+ populates it. One schema serves both.
|
||||
expect(result.rows).toHaveLength(1);
|
||||
expect(result.rows[0]?.is_nullable).toBe('YES');
|
||||
expect(result.rows[0]?.data_type).toBe('text');
|
||||
});
|
||||
|
||||
it('backfills ONLY credential rows with the synthetic issuer (#1395 upgrade path)', async () => {
|
||||
// Simulate an upgraded install: migrate through 0016 only, seed pre-issuer
|
||||
// rows (one credential, one oauth), then apply 0017 and discriminate.
|
||||
const client = (handle.db as unknown as { $client: PgliteExec }).$client;
|
||||
|
||||
// Migrate to 0016 by replaying every ledger file except 0017 — the ledger
|
||||
// table gates re-application, so a plain replay of 0000..0016 is enough.
|
||||
const fs = await import('node:fs');
|
||||
const path = await import('node:path');
|
||||
const dir = path.join(import.meta.dirname, '..', 'drizzle');
|
||||
const files = fs
|
||||
.readdirSync(dir)
|
||||
.filter((f) => /^\d{4}_.*\.sql$/.test(f) && f < '0017')
|
||||
.sort();
|
||||
for (const f of files) {
|
||||
const raw = fs.readFileSync(path.join(dir, f), 'utf-8');
|
||||
for (const stmt of raw.split('--> statement-breakpoint')) {
|
||||
const trimmed = stmt.trim();
|
||||
if (trimmed) await client.exec(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
await client.exec(`
|
||||
INSERT INTO users (id, name, email, email_verified, created_at, updated_at)
|
||||
VALUES ('u1', 'Legacy User', '[email protected]', true, now(), now());
|
||||
INSERT INTO accounts (id, account_id, provider_id, user_id, created_at, updated_at)
|
||||
VALUES
|
||||
('a1', '[email protected]', 'credential', 'u1', now(), now()),
|
||||
('a2', 'oauth-provider-1', 'google', 'u1', now(), now());
|
||||
`);
|
||||
|
||||
// Apply 0017 (column + backfill).
|
||||
const sql0017 = fs.readFileSync(path.join(dir, '0017_accounts_issuer.sql'), 'utf-8');
|
||||
for (const stmt of sql0017.split('--> statement-breakpoint')) {
|
||||
const trimmed = stmt.trim();
|
||||
if (trimmed) await client.exec(trimmed);
|
||||
}
|
||||
|
||||
const rows = (await handle.db.execute(sql`
|
||||
SELECT provider_id, issuer FROM accounts ORDER BY id
|
||||
`)) as unknown as { rows: Array<{ provider_id: string; issuer: string | null }> };
|
||||
|
||||
const byProvider = new Map(rows.rows.map((r) => [r.provider_id, r.issuer]));
|
||||
// Credential rows get better-auth's synthetic local issuer — the value
|
||||
// sign-in filters on (better-auth dist createLocalAccountIssuer).
|
||||
expect(byProvider.get('credential')).toBe('local:credential');
|
||||
// OAuth rows are LEFT NULL: better-auth owns their issuer semantics going
|
||||
// forward (each provider's real issuer on its next flow).
|
||||
expect(byProvider.get('google')).toBeNull();
|
||||
});
|
||||
|
||||
it('surfaces statement-level error context on failure and leaves no ledger row', async () => {
|
||||
// Pre-create a `users` table that conflicts with migration 0000's CREATE TABLE,
|
||||
// forcing it to fail without IF NOT EXISTS.
|
||||
const client = (handle.db as unknown as { $client: PgliteExec }).$client;
|
||||
await client.exec('CREATE TABLE users (sentinel text)');
|
||||
|
||||
await expect(runPgliteMigrations(handle)).rejects.toThrow(
|
||||
/migration hash=[a-f0-9]+ statement #\d+ failed/,
|
||||
);
|
||||
|
||||
// Ledger should be empty — partial application must not pretend to be complete.
|
||||
const ledger = (await handle.db.execute(
|
||||
sql`SELECT count(*)::int AS count FROM drizzle.__drizzle_migrations`,
|
||||
)) as unknown as { rows: Array<{ count: number }> };
|
||||
expect(ledger.rows[0]?.count).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user