File size: 8,464 Bytes
57b63b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35243f1
57b63b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35243f1
57b63b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35243f1
 
 
 
 
 
57b63b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { test } from "node:test";

test("optimized workspace reads preserve authorization, scores and fresh permission checks", { timeout: 30_000 }, async () => {
  const owner = "11111111-1111-4111-8111-111111111111";
  const other = "22222222-2222-4222-8222-222222222222";
  const playerId = "33333333-3333-4333-8333-333333333333";
  const otherPlayerId = "44444444-4444-4444-8444-444444444444";
  const calls: { method: string; url: URL }[] = [];
  let role = "user";
  let revoked = false;
  let authUnavailable = false;
  let failActions = false;
  const user = { id: owner, email: "test@example.com", email_confirmed_at: new Date().toISOString(), user_metadata: {} };
  const plan = { subscription_id: "plan", plan: "Pro", price: 10, player_limit: 5, session_limit: 10 };
  const player = { player_id: playerId, user_id: owner, fname: "Own", lname: "Player", bdate: "2000-01-01", gender: "male", image_path: null };
  const sessions = [
    { session_id: "s1", player_id: playerId, user_id: owner, created_at: "2026-01-02T00:00:00Z", status: "completed", name: "Own session", player },
    { session_id: "s2", player_id: otherPlayerId, user_id: other, created_at: "2026-01-01T00:00:00Z", status: "completed", name: "Other session", player: { fname: "Other" } },
  ];
  const actions = [
    { action_id: "a1", session_id: "s1", type: "Pass", annotation: { score: 7 } },
    { action_id: "a2", session_id: "s1", type: "Pass", annotation: null },
    { action_id: "a3", session_id: "s2", type: "Pass", annotation: { score: 10 } },
  ];
  const upstream = createServer((req, res) => {
    const url = new URL(req.url!, "http://stub");
    calls.push({ method: req.method!, url });
    res.setHeader("Content-Type", "application/json");
    if (url.pathname === "/auth/v1/user") {
      if (authUnavailable) { res.statusCode = 503; return res.end(JSON.stringify({ message: "Temporarily unavailable" })); }
      res.statusCode = revoked ? 401 : 200;
      return res.end(JSON.stringify(revoked ? { message: "Revoked" } : user));
    }
    if (url.pathname === "/auth/v1/token") {
      return res.end(JSON.stringify({ user, access_token: "access", refresh_token: "refresh", expires_in: 3600, token_type: "bearer" }));
    }
    const table = url.pathname.split("/").at(-1);
    if (table === "actions" && failActions) {
      res.statusCode = 400;
      return res.end(JSON.stringify({ message: "Unavailable" }));
    }
    let rows: Record<string, unknown>[] = table === "users" ? [{ user_id: owner, email: user.email, account_role: role }]
      : table === "users_subscriptions" ? [{ user_id: owner, subscription_id: "plan", startdate: "2000-01-01", enddate: "2099-01-01", num_players: 1, num_sessions: 1, plan }]
      : table === "players" ? [player, { player_id: otherPlayerId, user_id: other, fname: "Other" }]
      : table === "sessions" ? sessions : table === "actions" ? actions : [];
    for (const [key, filter] of url.searchParams) {
      if (filter.startsWith("eq.")) rows = rows.filter((row) => String(row[key]) === filter.slice(3));
      if (filter.startsWith("in.(")) {
        const values = filter.slice(4, -1).split(",");
        rows = rows.filter((row) => values.includes(String(row[key])));
      }
    }
    const from = Number(url.searchParams.get("offset") ?? 0);
    const limit = Number(url.searchParams.get("limit") ?? rows.length);
    res.end(JSON.stringify(rows.slice(from, from + limit)));
  });
  upstream.listen(0, "127.0.0.1");
  await once(upstream, "listening");
  const portProbe = createServer();
  portProbe.listen(0, "127.0.0.1");
  await once(portProbe, "listening");
  const port = (portProbe.address() as AddressInfo).port;
  await new Promise<void>((resolve) => portProbe.close(() => resolve()));
  const origin = `http://127.0.0.1:${port}`;
  const child = spawn(process.execPath, ["--import", "tsx", "src/server.ts"], {
    env: { ...process.env, NODE_ENV: "test", PORT: String(port), FRONTEND_URL: origin, BACKEND_URL: origin,
      SUPABASE_URL: `http://127.0.0.1:${(upstream.address() as AddressInfo).port}`,
      SUPABASE_ANON_KEY: "test-anon", SUPABASE_SERVICE_ROLE_KEY: "test-service", ANALYSIS_PROXY_SECRET: "x".repeat(32) },
    stdio: ["ignore", "pipe", "pipe"],
  });
  try {
    await new Promise<void>((resolve, reject) => {
      child.stdout.on("data", (data) => { if (String(data).includes("API listening")) resolve(); });
      child.once("exit", (code) => reject(new Error(`API exited: ${code}`)));
      child.once("error", reject);
    });
    const request = (path: string, init: RequestInit = {}) => fetch(`${origin}${path}`, {
      ...init, signal: AbortSignal.timeout(10000), headers: { Cookie: "sp_access=access", Origin: origin, ...init.headers },
    }).catch((error) => { throw new Error(`Request failed: ${path}`, { cause: error }); });
    assert.equal((await fetch(`${origin}/api/players-overview`)).status, 401);
    authUnavailable = true;
    const outage = await request("/api/auth/me", { headers: { Cookie: "sp_access=access; sp_refresh=refresh" } });
    assert.equal(outage.status, 500);
    assert.equal(outage.headers.get("set-cookie"), null, "an outage must preserve session cookies");
    assert.equal(calls.some((call) => call.url.pathname === "/auth/v1/token"), false, "an outage must not rotate refresh tokens");
    authUnavailable = false;
    const meStart = calls.length;
    const me = await (await request("/api/auth/me")).json();
    assert.equal(me.access.active, true);
    assert.equal(me.access.subscription.plan, "Pro");
    assert.equal(calls.length - meStart, 3); // auth + role + joined assignment/plan
    const rosterResponse = await request("/api/players-overview");
    assert.equal(rosterResponse.status, 200);
    const roster = (await rosterResponse.json()).players;
    assert.equal(roster.length, 1);
    assert.equal(roster[0].scores.overall, 70);
    assert.equal(roster[0].actionCount, 2);
    assert.equal(roster[0].scores.scoredActions, 1);
    const list = (await (await request("/api/sessions-overview")).json()).sessions;
    assert.equal(list.length, 1);
    assert.equal(list[0].playerName, "Own Player");
    assert.equal(list[0].actionBreakdown.Pass, 2);
    const detail = await (await request(`/api/players-overview/${playerId}`)).json();
    assert.equal(detail.scores.overall, 70);
    assert.equal(detail.playerCount, 1);
    assert.equal((await request(`/api/players-overview/${otherPlayerId}`)).status, 404);
    const billing = await (await request("/api/plan-billing")).json();
    assert.equal(billing.subscription.name, "Pro");
    assert.equal(billing.usage.sessions, 1);
    assert.ok(!calls.some(({ url }) => url.pathname.endsWith("/annotations")));
    const login = await request("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: user.email, password: "password123" }) });
    assert.equal(login.status, 200);
    assert.ok(!calls.some(({ method }) => method === "PATCH"));
    role = "admin";
    const adminPlayers = await (await request("/api/admin/players")).json();
    assert.equal(adminPlayers.players.find((row: { player_id: string }) => row.player_id === playerId).sessionCount, 1);
    const adminSessions = await (await request("/api/admin/sessions")).json();
    assert.equal(adminSessions.sessions.find((row: { session_id: string }) => row.session_id === "s1").actionCount, 2);
    const adminUsers = await (await request("/api/admin/users")).json();
    assert.equal(adminUsers.users[0].dataCounts.sessions, 1);
    const adminRows = (await (await request("/api/players-overview")).json()).players;
    assert.equal(adminRows.length, 2);
    role = "user";
    assert.equal((await (await request("/api/players-overview")).json()).players.length, 1);
    failActions = true;
    assert.equal((await request("/api/sessions-overview")).status, 500);
    revoked = true;
    assert.equal((await request("/api/sessions-overview")).status, 401);
  } finally {
    if (child.exitCode === null && child.signalCode === null) {
      const exited = once(child, "exit");
      child.kill();
      await exited;
    }
    upstream.closeAllConnections();
    await new Promise<void>((resolve) => upstream.close(() => resolve()));
  }
});