mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
feat: add reset progress and flagged media analysis
This commit is contained in:
1 parent
a7e29682c1
commit
7e686c147a
15 files changed
+1049
-215
No files matched your search
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user