Shoraky commited on
Commit
dffa291
·
1 Parent(s): 5208038

Verify cross-roster admin analysis and log actor role

Browse files
Files changed (2) hide show
  1. app.py +2 -2
  2. tests/analysis-admin.test.ts +70 -0
app.py CHANGED
@@ -250,8 +250,8 @@ app = FastAPI(title="Sporalize AI", lifespan=lifespan)
250
  @app.exception_handler(httpx.HTTPStatusError)
251
  async def backend_error(request: Request, error: httpx.HTTPStatusError):
252
  status = error.response.status_code
253
- logger.error("Analysis backend request failed: path=%s status=%s response=%s",
254
- request.url.path, status, error.response.text[:500])
255
  try:
256
  backend_body = error.response.json()
257
  except ValueError:
 
250
  @app.exception_handler(httpx.HTTPStatusError)
251
  async def backend_error(request: Request, error: httpx.HTTPStatusError):
252
  status = error.response.status_code
253
+ logger.error("Analysis backend request failed: path=%s status=%s actor_is_admin=%s response=%s",
254
+ request.url.path, status, getattr(request.state, "is_admin", False), error.response.text[:500])
255
  try:
256
  backend_body = error.response.json()
257
  except ValueError:
tests/analysis-admin.test.ts ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import assert from "node:assert/strict";
2
+ import { createServer } from "node:http";
3
+ import type { AddressInfo } from "node:net";
4
+ import { test } from "node:test";
5
+ import express from "express";
6
+ import type { SupabaseClient } from "@supabase/supabase-js";
7
+
8
+ test("an administrator can start analysis for another user's player", async () => {
9
+ Object.assign(process.env, {
10
+ NODE_ENV: "test",
11
+ FRONTEND_URL: "http://127.0.0.1:3000",
12
+ BACKEND_URL: "http://127.0.0.1:4000",
13
+ ANALYSIS_PROXY_SECRET: "test-analysis-proxy-secret-32-chars",
14
+ SUPABASE_URL: "http://127.0.0.1:54321",
15
+ SUPABASE_ANON_KEY: "test-anon-key",
16
+ SUPABASE_SERVICE_ROLE_KEY: "test-service-role-key",
17
+ });
18
+ const { analysisRouter } = await import("../src/analysis.js");
19
+ const adminId = "11111111-1111-4111-8111-111111111111";
20
+ const ownerId = "22222222-2222-4222-8222-222222222222";
21
+ const playerId = "33333333-3333-4333-8333-333333333333";
22
+ const clientId = "44444444-4444-4444-8444-444444444444";
23
+ const filters: [string, string][] = [];
24
+ const rpcCalls: { name: string; args: Record<string, unknown> }[] = [];
25
+ const db = {
26
+ from(table: string) {
27
+ assert.equal(table, "players");
28
+ const query = {
29
+ select() { return query; },
30
+ eq(column: string, value: string) { filters.push([column, value]); return query; },
31
+ async maybeSingle() { return { data: { player_id: playerId, user_id: ownerId }, error: null }; },
32
+ };
33
+ return query;
34
+ },
35
+ async rpc(name: string, args: Record<string, unknown>) {
36
+ rpcCalls.push({ name, args });
37
+ return { error: null };
38
+ },
39
+ } as unknown as SupabaseClient;
40
+ const secret = "test-analysis-proxy-secret-32-chars";
41
+ const app = express();
42
+ app.use("/internal/analysis", analysisRouter(db, secret));
43
+ const server = createServer(app);
44
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
45
+ try {
46
+ const port = (server.address() as AddressInfo).port;
47
+ const response = await fetch(`http://127.0.0.1:${port}/internal/analysis`, {
48
+ method: "POST",
49
+ headers: {
50
+ "content-type": "application/json",
51
+ "x-sporalize-proxy-secret": secret,
52
+ "x-sporalize-user-id": adminId,
53
+ "x-sporalize-admin": "true",
54
+ },
55
+ body: JSON.stringify({ clientId, playerId, name: "Admin analysis" }),
56
+ });
57
+ assert.equal(response.status, 200);
58
+ assert.deepEqual(await response.json(), {
59
+ session_id: clientId, player_id: playerId, user_id: ownerId,
60
+ by_admin: true, actor_is_admin: true,
61
+ });
62
+ assert.deepEqual(filters, [["player_id", playerId]]);
63
+ assert.deepEqual(rpcCalls, [{ name: "admin_create_analysis_session", args: {
64
+ p_session_id: clientId, p_user_id: ownerId, p_player_id: playerId,
65
+ p_name: "Admin analysis", p_path: `sessions/${clientId}`, p_by_admin: true,
66
+ } }]);
67
+ } finally {
68
+ await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
69
+ }
70
+ });