mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
feat(dares): seed 150-item catalogue
This commit is contained in:
1 parent
d998e32047
commit
439f506fff
5 files changed
+506
-1
No files matched your search
@@ -1,5 +1,8 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { readdir, readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import { Pool } from 'pg';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { FREE_WEEKLY_SEND_LIMIT, PostgresDaresRepository } from '../src/domains/dares/repository.js';
|
||||
@@ -82,6 +85,130 @@ describe.skipIf(!url || !local)('dares + PostgreSQL', () => {
|
||||
await expect(repo.send(sender, { dare_id: unreviewedDare })).rejects.toMatchObject({ code: 'dare_not_found' });
|
||||
});
|
||||
|
||||
it('seeds the 150-item editorial catalogue with fail-closed review states', async () => {
|
||||
const seeded = await pool.query<{
|
||||
name: string; is_premium: boolean; is_public: boolean; is_explicit: boolean;
|
||||
content_status: string; min_intensity: number; max_intensity: number; avg_intensity: string; dare_count: string;
|
||||
}>(`
|
||||
select dp.name, dp.is_premium, dp.is_public, dp.is_explicit, dp.content_status,
|
||||
dp.min_intensity, dp.max_intensity, dp.avg_intensity::text,
|
||||
count(d.id)::text as dare_count
|
||||
from dare_packs dp
|
||||
join dares d on d.pack_id = dp.id
|
||||
where dp.id between '1f5e0000-0000-4000-8000-100000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-100000000010'::uuid
|
||||
group by dp.id
|
||||
order by dp.sort_order
|
||||
`);
|
||||
|
||||
expect(seeded.rows).toEqual([
|
||||
{ name: 'Little Things', is_premium: false, is_public: true, is_explicit: false, content_status: 'allowed', min_intensity: 1, max_intensity: 1, avg_intensity: '1.00', dare_count: '15' },
|
||||
{ name: 'Make Me Laugh', is_premium: false, is_public: true, is_explicit: false, content_status: 'allowed', min_intensity: 1, max_intensity: 2, avg_intensity: '1.33', dare_count: '15' },
|
||||
{ name: 'Sweet & Romantic', is_premium: false, is_public: true, is_explicit: false, content_status: 'allowed', min_intensity: 1, max_intensity: 2, avg_intensity: '1.80', dare_count: '15' },
|
||||
{ name: 'Surprise Me', is_premium: true, is_public: true, is_explicit: false, content_status: 'allowed', min_intensity: 1, max_intensity: 2, avg_intensity: '1.80', dare_count: '15' },
|
||||
{ name: 'Flirty Messages', is_premium: true, is_public: false, is_explicit: false, content_status: 'unreviewed', min_intensity: 2, max_intensity: 3, avg_intensity: '2.47', dare_count: '15' },
|
||||
{ name: 'Long-Distance Heat', is_premium: true, is_public: false, is_explicit: true, content_status: 'unreviewed', min_intensity: 2, max_intensity: 3, avg_intensity: '2.67', dare_count: '15' },
|
||||
{ name: 'Risky Photos & Audio', is_premium: true, is_public: false, is_explicit: true, content_status: 'unreviewed', min_intensity: 3, max_intensity: 4, avg_intensity: '3.53', dare_count: '15' },
|
||||
{ name: 'Hands On', is_premium: true, is_public: false, is_explicit: true, content_status: 'unreviewed', min_intensity: 3, max_intensity: 4, avg_intensity: '3.40', dare_count: '15' },
|
||||
{ name: 'After Dark', is_premium: true, is_public: false, is_explicit: true, content_status: 'unreviewed', min_intensity: 4, max_intensity: 4, avg_intensity: '4.00', dare_count: '15' },
|
||||
{ name: 'Power Play', is_premium: true, is_public: false, is_explicit: true, content_status: 'unreviewed', min_intensity: 5, max_intensity: 5, avg_intensity: '5.00', dare_count: '15' },
|
||||
]);
|
||||
|
||||
const distribution = await pool.query<{
|
||||
total: string; free: string; premium: string; allowed: string; unreviewed: string; duplicate_texts: string; stat_mismatches: string;
|
||||
}>(`
|
||||
select count(*)::text as total,
|
||||
count(*) filter (where not dp.is_premium)::text as free,
|
||||
count(*) filter (where dp.is_premium)::text as premium,
|
||||
count(*) filter (where d.content_status = 'allowed')::text as allowed,
|
||||
count(*) filter (where d.content_status = 'unreviewed')::text as unreviewed,
|
||||
(select count(*)::text from (
|
||||
select text from dares
|
||||
where id between '1f5e0000-0000-4000-8000-000000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-000000000150'::uuid
|
||||
group by text having count(*) > 1
|
||||
) duplicates) as duplicate_texts,
|
||||
(select count(*)::text from (
|
||||
select dp.id
|
||||
from dare_packs dp
|
||||
join dares expected on expected.pack_id = dp.id
|
||||
where dp.id between '1f5e0000-0000-4000-8000-100000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-100000000010'::uuid
|
||||
group by dp.id
|
||||
having dp.min_intensity is distinct from min(expected.intensity)
|
||||
or dp.max_intensity is distinct from max(expected.intensity)
|
||||
or dp.avg_intensity is distinct from round(avg(expected.intensity)::numeric, 2)
|
||||
) mismatches) as stat_mismatches
|
||||
from dares d
|
||||
join dare_packs dp on dp.id = d.pack_id
|
||||
where d.id between '1f5e0000-0000-4000-8000-000000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-000000000150'::uuid
|
||||
`);
|
||||
expect(distribution.rows[0]).toEqual({ total: '150', free: '45', premium: '105', allowed: '60', unreviewed: '90', duplicate_texts: '0', stat_mismatches: '0' });
|
||||
|
||||
const provenance = await pool.query<{ reviewed_rows: string; review_records: string; attributed_records: string }>(`
|
||||
select
|
||||
(select count(*)::text from (
|
||||
select content_reviewed_at
|
||||
from dare_packs
|
||||
where id between '1f5e0000-0000-4000-8000-100000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-100000000004'::uuid
|
||||
union all
|
||||
select content_reviewed_at
|
||||
from dares
|
||||
where id between '1f5e0000-0000-4000-8000-000000000001'::uuid
|
||||
and '1f5e0000-0000-4000-8000-000000000060'::uuid
|
||||
) reviewed where content_reviewed_at = '2026-09-05T16:00:00Z'::timestamptz)::text as reviewed_rows,
|
||||
(select count(*)::text from content_reviews
|
||||
where (entity_type = 'dare_packs' and entity_id between '1f5e0000-0000-4000-8000-100000000001'::uuid and '1f5e0000-0000-4000-8000-100000000004'::uuid)
|
||||
or (entity_type = 'dares' and entity_id between '1f5e0000-0000-4000-8000-000000000001'::uuid and '1f5e0000-0000-4000-8000-000000000060'::uuid)) as review_records,
|
||||
(select count(*)::text from content_reviews
|
||||
where ((entity_type = 'dare_packs' and entity_id between '1f5e0000-0000-4000-8000-100000000001'::uuid and '1f5e0000-0000-4000-8000-100000000004'::uuid)
|
||||
or (entity_type = 'dares' and entity_id between '1f5e0000-0000-4000-8000-000000000001'::uuid and '1f5e0000-0000-4000-8000-000000000060'::uuid))
|
||||
and changed_by is not null) as attributed_records
|
||||
`);
|
||||
expect(provenance.rows[0]).toEqual({ reviewed_rows: '64', review_records: '64', attributed_records: '0' });
|
||||
});
|
||||
|
||||
it('is discovered by Drizzle and fails closed on a conflicting catalogue ID', async () => {
|
||||
const migrationSchema = `dare_migrator_${randomUUID().replaceAll('-', '')}`;
|
||||
await admin.query(`create schema "${migrationSchema}"`);
|
||||
const migrationUrl = new URL(url!);
|
||||
migrationUrl.searchParams.set('options', `-c search_path=${migrationSchema}`);
|
||||
const migrationPool = new Pool({ connectionString: migrationUrl.toString() });
|
||||
const dir = new URL('../drizzle/', import.meta.url);
|
||||
|
||||
try {
|
||||
await migrate(drizzle(migrationPool), { migrationsFolder: fileURLToPath(dir) });
|
||||
const discovered = await migrationPool.query<{ count: string }>(
|
||||
"select count(*)::text as count from dares where id between '1f5e0000-0000-4000-8000-000000000001'::uuid and '1f5e0000-0000-4000-8000-000000000150'::uuid",
|
||||
);
|
||||
expect(discovered.rows[0]?.count).toBe('150');
|
||||
|
||||
const seed = await readFile(new URL('0023_dare_catalogue_150.sql', dir), 'utf8');
|
||||
const applySeed = async () => {
|
||||
for (const statement of seed.split('--> statement-breakpoint')) {
|
||||
if (statement.trim()) await migrationPool.query(statement);
|
||||
}
|
||||
};
|
||||
await applySeed();
|
||||
await migrationPool.query(
|
||||
"update content_reviews set reason = 'Conflicting review' where id = '2f5e0000-0000-4000-8000-000000000001'::uuid",
|
||||
);
|
||||
await expect(applySeed()).rejects.toThrow(/division by zero|review provenance/);
|
||||
await migrationPool.query(
|
||||
"update content_reviews set reason = 'Reviewed with Little Things pack' where id = '2f5e0000-0000-4000-8000-000000000001'::uuid",
|
||||
);
|
||||
await migrationPool.query(
|
||||
"update dares set text = 'Conflicting dare' where id = '1f5e0000-0000-4000-8000-000000000001'::uuid",
|
||||
);
|
||||
await expect(applySeed()).rejects.toThrow(/division by zero/);
|
||||
} finally {
|
||||
await migrationPool.end();
|
||||
await admin.query(`drop schema if exists "${migrationSchema}" cascade`);
|
||||
}
|
||||
});
|
||||
|
||||
it('gates premium packs and custom dares, and unlocks when either partner pays', async () => {
|
||||
await expect(repo.listPackDares(sender, premiumPack)).rejects.toMatchObject({ code: 'premium_required' });
|
||||
await expect(repo.send(sender, { dare_id: premiumDare })).rejects.toMatchObject({ code: 'premium_required' });
|
||||
|
||||
Reference in new issue
Block a user