Public: anyone can read this; the crew can change it.

A test pins "Fetch now" on a prompt source: an unknown source, or a forge whose prompts routine is off, is refused and nothing runs #7126

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7119-a-test-pins-fetch-now-on-a-prompt-source → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/7119-a-test-pins-fetch-now-on-a-prompt-source was merged into main as 7dc46f5.

Review

Sign in to review this change.
1 file changed +171 −0
test/prompt-fetch-now.test.ts added +171
@@ -0,0 +1,171 @@
1 /**
2 * "Fetch now" on a prompt source (`runPromptSource`): an admin's press runs
3 * the routine once; no routine, an unknown or foreign source, or a writer
4 * who is not an admin runs nothing. Instagram is a stub that counts calls.
5 */
6
7 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8 import { mkdtemp, rm } from "node:fs/promises";
9 import { tmpdir } from "node:os";
10 import { join } from "node:path";
11 import { runPromptSource } from "../src/actions/prompts.ts";
12 import { createOrg, createTeam, addTeamMember } from "../src/actions/orgs.ts";
13 import { createRepo } from "../src/actions/repos.ts";
14 import { createApp, type App } from "../src/app.ts";
15 import { getOrg } from "../src/core/orgs.ts";
16 import { addPromptSource } from "../src/core/prompts.ts";
17 import type { Repo } from "../src/core/repos.ts";
18 import { createUser, type User } from "../src/core/users.ts";
19 import {
20 startPromptRoutine,
21 type PromptRoutineHandle,
22 } from "../src/crew/prompts.ts";
23 import type { InstagramClient } from "../src/crew/instagram.ts";
24 import { quietLogs } from "./helpers/globals.ts";
25
26 quietLogs();
27
28 let dataDir: string;
29 let app: App;
30 let bare: App;
31 let bareDir: string;
32 let routine: PromptRoutineHandle;
33 let alice: User;
34 let bob: User;
35 let repo: Repo;
36 let other: Repo;
37 let sourceId = "";
38 let otherSourceId = "";
39 let bareSourceId = "";
40 let bareRepo: Repo;
41 let bareAlice: User;
42 let fetches = 0;
43
44 /** Counts `me` calls: one run asks Instagram who it is exactly once. */
45 const instagram: InstagramClient = {
46 me: () => {
47 fetches += 1;
48 return Promise.resolve({ id: "1", username: "handle" });
49 },
50 latestReel: () => Promise.resolve(undefined),
51 comments: () => Promise.resolve([]),
52 refresh: () => Promise.resolve({ token: "fresh" }),
53 };
54
55 const user = async (a: App, username: string, adminIfFirst = false): Promise<User> => {
56 const made = await createUser(a.db, {
57 origin: "seed",
58 trusted: true,
59 username,
60 email: `${username}@forge.local`,
61 password: "a-good-passphrase",
62 adminIfFirst,
63 });
64 if (!made.ok) throw new Error(made.error);
65 return made.value;
66 };
67
68 const source = (a: App, repoId: string, token: string, by: User): string => {
69 const added = addPromptSource(a.db, a === app ? dataDir : bareDir, {
70 repoId,
71 kind: "instagram",
72 token,
73 atUtc: "09:00",
74 createdBy: by.id,
75 });
76 if (!added.ok) throw new Error(added.error);
77 return added.value.id;
78 };
79
80 beforeAll(async () => {
81 dataDir = await mkdtemp(join(tmpdir(), "forge-fetchnow-"));
82 bareDir = await mkdtemp(join(tmpdir(), "forge-fetchnow-bare-"));
83 app = createApp({ dataDir, port: 0 });
84 bare = createApp({ dataDir: bareDir, port: 0 });
85 routine = startPromptRoutine(app, {
86 client: instagram,
87 everyMs: 3_600_000,
88 });
89 alice = await user(app, "alice", true);
90 bob = await user(app, "bob");
91
92 const org = createOrg(app, alice, "acme");
93 if (!org.ok) throw new Error(org.error);
94 const acme = getOrg(app.db, "acme")!;
95 const devs = createTeam(app, alice, acme, "devs", "write");
96 if (!devs.ok) throw new Error(devs.error);
97 addTeamMember(app, alice, acme, devs.value, "bob");
98 const made = await createRepo(app, alice, {
99 owner: "acme",
100 name: "game",
101 visibility: "public",
102 });
103 if (!made.ok) throw new Error(String(made.error));
104 repo = made.value;
105 const second = await createRepo(app, alice, {
106 name: "elsewhere",
107 visibility: "public",
108 });
109 if (!second.ok) throw new Error(String(second.error));
110 other = second.value;
111 sourceId = source(app, repo.id, "token-one", alice);
112 otherSourceId = source(app, other.id, "token-two", alice);
113
114 bareAlice = await user(bare, "alice", true);
115 const bareMade = await createRepo(bare, bareAlice, {
116 name: "game",
117 visibility: "public",
118 });
119 if (!bareMade.ok) throw new Error(String(bareMade.error));
120 bareRepo = bareMade.value;
121 bareSourceId = source(bare, bareRepo.id, "token-three", bareAlice);
122 });
123
124 afterAll(async () => {
125 await routine.stop();
126 app.close();
127 bare.close();
128 await rm(dataDir, { recursive: true, force: true });
129 await rm(bareDir, { recursive: true, force: true });
130 });
131
132 describe("Fetch now", () => {
133 test("an admin's press runs the routine once", async () => {
134 fetches = 0;
135 const ran = await runPromptSource(app, alice, repo, sourceId);
136 expect(ran.ok).toBe(true);
137 if (ran.ok) {
138 expect(ran.value.id).toBe(sourceId);
139 expect(ran.value.account).toBe("handle");
140 }
141 expect(fetches).toBe(1);
142 });
143
144 test("a forge with no prompts routine answers not_running and fetches nothing", async () => {
145 fetches = 0;
146 const ran = await runPromptSource(bare, bareAlice, bareRepo, bareSourceId);
147 expect(ran).toEqual({ ok: false, error: "not_running" });
148 expect(fetches).toBe(0);
149 });
150
151 test("another repository's source is no_such_source and nothing runs", async () => {
152 fetches = 0;
153 const ran = await runPromptSource(app, alice, repo, otherSourceId);
154 expect(ran).toEqual({ ok: false, error: "no_such_source" });
155 expect(await runPromptSource(app, alice, repo, "nope")).toEqual({
156 ok: false,
157 error: "no_such_source",
158 });
159 expect(fetches).toBe(0);
160 });
161
162 test("a writer who is not an admin is refused and nothing runs", async () => {
163 fetches = 0;
164 const ran = await runPromptSource(app, bob, repo, sourceId);
165 expect(ran.ok).toBe(false);

Assert the exact refusal, expect(ran).toEqual({ok:false,error:"not_found"}), as the other cases do. ok:false alone also passes for an unrelated failure.

166 expect(fetches).toBe(0);
167 const anon = await runPromptSource(app, undefined, repo, sourceId);
168 expect(anon.ok).toBe(false);
169 expect(fetches).toBe(0);
170 });
171 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs