mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Replace the Supabase application data plane with the Node/PostgreSQL API, migrate mobile, admin, web, and MCP callers, add controlled cutover tooling, and define hardened Dokploy infrastructure.
85 lines
4.1 KiB
TypeScript
85 lines
4.1 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { Pool } from 'pg';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import { PostgresProfileSettingsRepository } from '../src/domains/profile-settings/repository.js';
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
const localDatabase = databaseUrl ? ['127.0.0.1', 'localhost', '::1'].includes(new URL(databaseUrl).hostname) : false;
|
|
if (databaseUrl && !localDatabase) throw new Error('Profile settings integration tests only permit a localhost DATABASE_URL');
|
|
|
|
describe.skipIf(!databaseUrl || !localDatabase)('PostgresProfileSettingsRepository', () => {
|
|
const adminPool = new Pool({ connectionString: databaseUrl });
|
|
const schema = `profile_settings_test_${randomUUID().replaceAll('-', '')}`;
|
|
const userId = '11111111-1111-4111-8111-111111111111';
|
|
let pool: Pool;
|
|
let repository: PostgresProfileSettingsRepository;
|
|
|
|
beforeAll(async () => {
|
|
await adminPool.query(`CREATE SCHEMA "${schema}"`);
|
|
const isolatedUrl = new URL(databaseUrl!);
|
|
isolatedUrl.searchParams.set('options', `-c search_path=${schema}`);
|
|
pool = new Pool({ connectionString: isolatedUrl.toString() });
|
|
repository = new PostgresProfileSettingsRepository(isolatedUrl.toString());
|
|
for (const migrationName of [
|
|
'0000_identity_and_feature_interests.sql',
|
|
'0001_couples.sql',
|
|
'0002_packs_catalog_progress.sql',
|
|
'0003_answers_matches.sql',
|
|
'0004_chat.sql',
|
|
'0005_profile_settings.sql',
|
|
'0006_media_storage.sql',
|
|
]) {
|
|
const migration = await readFile(new URL(`../drizzle/${migrationName}`, import.meta.url), 'utf8');
|
|
for (const statement of migration.split('--> statement-breakpoint')) {
|
|
if (statement.trim()) await pool.query(statement);
|
|
}
|
|
}
|
|
await pool.query('insert into profiles (id, name) values ($1, $2)', [userId, 'Alice']);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await repository.close();
|
|
await pool.end();
|
|
await adminPool.query(`DROP SCHEMA "${schema}" CASCADE`);
|
|
await adminPool.end();
|
|
});
|
|
|
|
it('persists profile, activity, public key, push token, and onboarding state', async () => {
|
|
await repository.updateProfile(userId, {
|
|
name: 'Alicia', gender: 'female', usage_reason: 'deeper_connection', hide_nsfw: true,
|
|
max_intensity: 2, show_explicit_content: false, onboarding_completed: true,
|
|
onboarding_version: 1, public_key_jwk: { kty: 'RSA' }, push_token: 'ExponentPushToken[test]',
|
|
});
|
|
await repository.updateLastActive(userId);
|
|
const row = (await pool.query('select * from profiles where id = $1', [userId])).rows[0];
|
|
expect(row).toMatchObject({
|
|
name: 'Alicia', gender: 'female', usage_reason: 'deeper_connection', hide_nsfw: true,
|
|
max_intensity: 2, show_explicit_content: false, onboarding_completed: true,
|
|
onboarding_version: 1, public_key_jwk: { kty: 'RSA' }, push_token: 'ExponentPushToken[test]',
|
|
});
|
|
expect(row.last_active_at).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('creates default preferences, updates one key, and leaves the others enabled', async () => {
|
|
const initial = await repository.getNotificationPreferences(userId);
|
|
expect(initial.messages_enabled).toBe(true);
|
|
const updated = await repository.updateNotificationPreference(userId, 'messages_enabled', false);
|
|
expect(updated.messages_enabled).toBe(false);
|
|
expect(updated.matches_enabled).toBe(true);
|
|
});
|
|
|
|
it('stores text feedback owned by the authenticated user', async () => {
|
|
const submitted = await repository.submitFeedback(userId, {
|
|
type: 'general', title: 'Hello', description: 'Useful feedback', device_info: { os: 'ios' },
|
|
});
|
|
const row = (await pool.query('select * from feedback where id = $1', [submitted.id])).rows[0];
|
|
expect(row).toMatchObject({ user_id: userId, type: 'general', title: 'Hello' });
|
|
const mediaColumn = await pool.query(
|
|
`select column_name from information_schema.columns
|
|
where table_schema = current_schema() and table_name = 'feedback' and column_name = 'screenshot_url'`,
|
|
);
|
|
expect(mediaColumn.rowCount).toBe(0);
|
|
});
|
|
});
|