feat: add reset progress and flagged media analysis

This commit is contained in:
Jamie Brunton committed 2026-01-03 13:08:23 +00:00
1 parent a7e29682c1
commit 7e686c147a
15 files changed
+1049 -215

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
{
"imports": {
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2",
"std/": "https://deno.land/std@0.224.0/"
},
"tasks": {
"test": "deno test --allow-net --allow-env --allow-read"
}
}
+48
View File
@@ -0,0 +1,48 @@
import { assertEquals } from "std/testing/asserts.ts";
import { createTestUser, createRedemptionCode, cleanup, adminClient, SUPABASE_URL } from "./utils.ts";
const FUNCTION_URL = `${SUPABASE_URL}/functions/v1/redeem-code`;
Deno.test("redeem-code: successfully redeems code for user", async () => {
const email = `test_redeem_${Date.now()}@example.com`;
let userIds: string[] = [];
let codeId: string | undefined;
try {
// 1. Setup User
const user = await createTestUser(email);
userIds = [user.id];
// 2. Setup Code
const codeData = await createRedemptionCode();
codeId = codeData.id;
// 3. Call Function
const res = await fetch(FUNCTION_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email,
code: codeData.code
})
});
const data = await res.json();
assertEquals(res.status, 200);
assertEquals(data.success, true);
// 4. Verify Profile is Premium
const { data: profile } = await adminClient
.from("profiles")
.select("is_premium")
.eq("id", user.id)
.single();
assertEquals(profile.is_premium, true);
} finally {
await cleanup(userIds);
if (codeId) await adminClient.from("redemption_codes").delete().eq("id", codeId);
}
});
@@ -0,0 +1,87 @@
import { assert, assertEquals } from "std/testing/asserts.ts";
import { createTestUser, createCouple, cleanup, signInUser, createTestQuestion, adminClient, SUPABASE_URL } from "./utils.ts";
const FUNCTION_URL = `${SUPABASE_URL}/functions/v1/submit-response`;
Deno.test("submit-response: creates match when both users answer yes", async () => {
const email1 = `test_user_1_${Date.now()}@example.com`;
const email2 = `test_user_2_${Date.now()}@example.com`;
let coupleId: string | undefined;
let questionId: string | undefined;
let packId: string | undefined;
let users: string[] = [];
try {
// 1. Setup Users and Couple
const user1 = await createTestUser(email1);
const user2 = await createTestUser(email2);
users = [user1.id, user2.id];
const couple = await createCouple(user1.id, user2.id);
coupleId = couple.id;
// 2. Setup Question
const qData = await createTestQuestion();
questionId = qData.question!.id;
packId = qData.packId;
// 3. Sign In Users to get tokens
const session1 = await signInUser(email1);
const session2 = await signInUser(email2);
assert(session1?.access_token, "User 1 should have access token");
assert(session2?.access_token, "User 2 should have access token");
// 4. User 1 submits 'yes'
const res1 = await fetch(FUNCTION_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${session1!.access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
question_id: questionId,
answer: "yes",
}),
});
const data1 = await res1.json();
assertEquals(res1.status, 200);
assertEquals(data1.match, null, "First response should not create match");
// 5. User 2 submits 'yes'
const res2 = await fetch(FUNCTION_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${session2!.access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
question_id: questionId,
answer: "yes",
}),
});
const data2 = await res2.json();
assertEquals(res2.status, 200);
assert(data2.match, "Second response should create match");
assertEquals(data2.match.match_type, "yes_yes");
// 6. Verify Match in DB
const { data: match } = await adminClient
.from("matches")
.select("*")
.eq("id", data2.match.id)
.single();
assert(match, "Match should exist in DB");
assertEquals(match.match_type, "yes_yes");
} finally {
// Cleanup
await cleanup(users, coupleId);
if (questionId) await adminClient.from("questions").delete().eq("id", questionId);
if (packId) await adminClient.from("question_packs").delete().eq("id", packId);
}
});
+113
View File
@@ -0,0 +1,113 @@
import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { assert } from "std/testing/asserts.ts";
export const SUPABASE_URL = Deno.env.get("SUPABASE_URL") || "http://127.0.0.1:54321";
export const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
if (!SUPABASE_SERVICE_ROLE_KEY) {
throw new Error("SUPABASE_SERVICE_ROLE_KEY is required");
}
export const adminClient = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
export async function createTestUser(email: string) {
const { data, error } = await adminClient.auth.admin.createUser({
email,
password: "password123",
email_confirm: true,
});
if (error) throw error;
if (!data.user) throw new Error("User not created");
return data.user;
}
export async function signInUser(email: string) {
const { data, error } = await adminClient.auth.signInWithPassword({
email,
password: "password123",
});
if (error) throw error;
return data.session;
}
export async function createTestQuestion(packId?: string) {
let finalPackId = packId;
if (!finalPackId) {
const { data: pack } = await adminClient.from("question_packs").insert({
name: "Test Pack " + Math.random(),
is_premium: false,
sort_order: 999
}).select().single();
finalPackId = pack!.id;
}
const { data: question } = await adminClient.from("questions").insert({
pack_id: finalPackId,
text: "Test Question?",
intensity: 1
}).select().single();
return { question, packId: finalPackId };
}
export async function createRedemptionCode() {
const code = "TEST-" + Math.random().toString(36).substring(7).toUpperCase();
const { data, error } = await adminClient.from("redemption_codes").insert({
code,
max_uses: 1,
active: true,
expires_at: new Date(Date.now() + 3600000).toISOString() // 1 hour
}).select().single();
if (error) throw error;
return data;
}
export async function createCouple(user1Id: string, user2Id: string) {
// 1. Create a couple
const { data: couple, error: coupleError } = await adminClient
.from("couples")
.insert({
// invite_code is unique, so generate a random one
invite_code: Math.random().toString(36).substring(7),
})
.select()
.single();
if (coupleError) throw coupleError;
// 2. Link users to couple
const { error: updateError } = await adminClient
.from("profiles")
.update({ couple_id: couple.id })
.in("id", [user1Id, user2Id]);
if (updateError) throw updateError;
return couple;
}
export async function cleanup(userIds: string[], coupleId?: string) {
// Delete couple (should cascade to matches, responses, etc. if configured, but let's be safe)
// Based on schema analysis, explicit cleanup is safer.
if (coupleId) {
await adminClient.from("matches").delete().eq("couple_id", coupleId);
await adminClient.from("responses").delete().eq("couple_id", coupleId);
await adminClient.from("couples").delete().eq("id", coupleId);
}
// Delete users (cascades to profiles)
for (const id of userIds) {
await adminClient.auth.admin.deleteUser(id);
}
}