Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/api/sessions/[sessionId]/__tests__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function makePatchReqRaw(
const mockRow: SessionRow = {
id: "sess_1",
account_id: "acc-uuid-1",
artist_id: null,
title: "Test session",
status: "running",
repo_owner: "acme",
Expand Down Expand Up @@ -186,6 +187,7 @@ describe("GET /api/sessions/[sessionId]", () => {
session: {
id: "sess_1",
userId: "acc-uuid-1",
artistId: null,
title: "Test session",
status: "running",
repoOwner: "acme",
Expand Down
1 change: 1 addition & 0 deletions lib/sessions/__tests__/baseSessionRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function baseSessionRow(overrides: Partial<Tables<"sessions">> = {}): Tab
return {
id: "sess_1",
account_id: "acc-uuid-1",
artist_id: null,
title: "Test session",
status: "running",
repo_owner: null,
Expand Down
19 changes: 19 additions & 0 deletions lib/sessions/__tests__/buildSessionInsertRow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,23 @@ describe("buildSessionInsertRow", () => {
});
expect(row.sandbox_state).toEqual({ type: "vercel" });
});

it("writes artist_id when artistId is provided", () => {
const row = buildSessionInsertRow({
accountId: "acc-1",
title: "Berlin",
cloneUrl: DEFAULT_CLONE,
artistId: "artist-uuid-1",
});
expect(row.artist_id).toBe("artist-uuid-1");
});

it("sets artist_id to null when artistId is omitted", () => {
const row = buildSessionInsertRow({
accountId: "acc-1",
title: "Berlin",
cloneUrl: DEFAULT_CLONE,
});
expect(row.artist_id).toBeNull();
});
});
25 changes: 25 additions & 0 deletions lib/sessions/__tests__/createSessionHandler.persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,31 @@ describe("createSessionHandler — persistence", () => {
expect(vi.mocked(insertSession).mock.calls[0][0].title).toBe("Hello world");
});

it("writes artist_id when body carries artistId, and exposes it on the response", async () => {
const artistId = "a25c5dc5-3eb2-4fff-9a5e-39e90c9d4f02";
vi.mocked(validateCreateSessionBody).mockResolvedValue(okValidated({ body: { artistId } }));
vi.mocked(insertSession).mockResolvedValue(baseSessionRow({ artist_id: artistId }));
vi.mocked(insertChat).mockResolvedValue(baseChatRow());

const res = await createSessionHandler(makeCreateSessionReq({ artistId }));
expect(res.status).toBe(200);

expect(vi.mocked(insertSession).mock.calls[0][0].artist_id).toBe(artistId);

const body = (await res.json()) as { session: { artistId: string | null } };
expect(body.session.artistId).toBe(artistId);
});

it("writes artist_id as null when artistId is omitted", async () => {
vi.mocked(validateCreateSessionBody).mockResolvedValue(okValidated());
vi.mocked(insertSession).mockResolvedValue(baseSessionRow());
vi.mocked(insertChat).mockResolvedValue(baseChatRow());

await createSessionHandler(makeCreateSessionReq({}));

expect(vi.mocked(insertSession).mock.calls[0][0].artist_id).toBeNull();
});

it("returns 500 when insertSession fails", async () => {
vi.mocked(validateCreateSessionBody).mockResolvedValue(okValidated());
vi.mocked(insertSession).mockResolvedValue(null);
Expand Down
23 changes: 23 additions & 0 deletions lib/sessions/__tests__/validateCreateSessionBody.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,27 @@ describe("validateCreateSessionBody", () => {
expect.objectContaining({ organizationId: orgId }),
);
});

it("accepts a valid artistId and surfaces it on the validated body", async () => {
vi.mocked(validateAuthContext).mockResolvedValue(okAuth);

const artistId = "a25c5dc5-3eb2-4fff-9a5e-39e90c9d4f02";
const result = await validateCreateSessionBody(req({ artistId }));
expect(result).not.toBeInstanceOf(NextResponse);
if (!(result instanceof NextResponse)) {
expect(result.body.artistId).toBe(artistId);
}
});

it("rejects a non-UUID artistId with 400", async () => {
vi.mocked(validateAuthContext).mockResolvedValue(okAuth);

const result = await validateCreateSessionBody(req({ artistId: "not-a-uuid" }));
expect(result).toBeInstanceOf(NextResponse);
if (result instanceof NextResponse) {
expect(result.status).toBe(400);
const body = (await result.json()) as { status: string; error: string };
expect(body.error).toMatch(/UUID/i);
}
});
});
11 changes: 9 additions & 2 deletions lib/sessions/buildSessionInsertRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ interface BuildSessionInsertRowInput {
* `cloneUrl`.
*/
cloneUrl: string;
/**
* Optional artist account this session belongs to. Backs the chat
* sidebar's artist filter; omitted for sessions not tied to an
* artist context.
*/
artistId?: string;
}

/**
Expand All @@ -18,14 +24,15 @@ interface BuildSessionInsertRowInput {
* the default / null-coalescing rules so the handler can stay focused
* on HTTP and persistence flow.
*
* @param input - The validated body, owning account id, resolved title, and resolved clone URL.
* @param input - The validated body, owning account id, resolved title, resolved clone URL, and optional artist id.
* @returns A row ready to pass to `insertSession`.
*/
export function buildSessionInsertRow(input: BuildSessionInsertRowInput): TablesInsert<"sessions"> {
const { accountId, title, cloneUrl } = input;
const { accountId, title, cloneUrl, artistId } = input;
return {
id: generateUUID(),
account_id: accountId,
artist_id: artistId ?? null,
title,
status: "running",
branch: null,
Expand Down
1 change: 1 addition & 0 deletions lib/sessions/createSessionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export async function createSessionHandler(request: NextRequest): Promise<NextRe
accountId: auth.accountId,
title,
cloneUrl,
artistId: body.artistId,
}),
);

Expand Down
1 change: 1 addition & 0 deletions lib/sessions/toSessionResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function toSessionResponse(row: Tables<"sessions">) {
return {
id: row.id,
userId: row.account_id,
artistId: row.artist_id,
title: row.title,
status: row.status,
repoOwner: row.repo_owner,
Expand Down
1 change: 1 addition & 0 deletions lib/sessions/validateCreateSessionBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { AuthContext } from "@/lib/auth/validateAuthContext";
export const createSessionBodySchema = z.object({
title: z.string().optional(),
organizationId: z.string().uuid("organizationId must be a valid UUID").optional(),
artistId: z.string().uuid("artistId must be a valid UUID").optional(),
});

export type CreateSessionBody = z.infer<typeof createSessionBodySchema>;
Expand Down
10 changes: 10 additions & 0 deletions types/database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2878,6 +2878,7 @@ export type Database = {
sessions: {
Row: {
account_id: string;
artist_id: string | null;
branch: string | null;
cached_diff: Json | null;
cached_diff_updated_at: string | null;
Expand Down Expand Up @@ -2907,6 +2908,7 @@ export type Database = {
};
Insert: {
account_id: string;
artist_id?: string | null;
branch?: string | null;
cached_diff?: Json | null;
cached_diff_updated_at?: string | null;
Expand Down Expand Up @@ -2936,6 +2938,7 @@ export type Database = {
};
Update: {
account_id?: string;
artist_id?: string | null;
branch?: string | null;
cached_diff?: Json | null;
cached_diff_updated_at?: string | null;
Expand Down Expand Up @@ -2971,6 +2974,13 @@ export type Database = {
referencedRelation: "accounts";
referencedColumns: ["id"];
},
{
foreignKeyName: "sessions_artist_id_fkey";
columns: ["artist_id"];
isOneToOne: false;
referencedRelation: "accounts";
referencedColumns: ["id"];
},
];
};
social_fans: {
Expand Down
Loading