profileShare

rasmusjy / profileshare

Read-only snapshot

No repository description.

main default branch 54 files Expires Sep 13, 2026, 9:06 AM
helpers.js 9,718 bytes
1 import request from "supertest";
2 import { createStore } from "../src/db.js";
3 import { createApp } from "../src/app.js";
4
5 export const repositories = [
6 {
7 id: 101,
8 name: "atlas",
9 fullName: "rasmus/atlas",
10 description: "A mapping tool",
11 private: true,
12 language: "JavaScript",
13 updatedAt: "2026-07-20T10:00:00.000Z",
14 defaultBranch: "main",
15 owner: "rasmus",
16 },
17 {
18 id: 202,
19 name: "ledger",
20 fullName: "rasmus/ledger",
21 description: "An expense journal",
22 private: true,
23 language: "TypeScript",
24 updatedAt: "2026-07-21T10:00:00.000Z",
25 defaultBranch: "main",
26 owner: "rasmus",
27 },
28 ];
29
30 function snapshot(repo) {
31 const headSha = `${repo.id}`.padEnd(40, "a");
32 const branchSha = `${repo.id + 1}`.padEnd(40, "c");
33 const tagSha = `${repo.id + 2}`.padEnd(40, "d");
34 const files = [
35 {
36 path: "README.md",
37 size: 114,
38 content: `# ${repo.name}\n\nSnapshot\n\n## Highlights\n\n- Read-only review\n- [Read the guide](docs/guide.md)\n\n<script>alert("x")</script>`,
39 truncated: false,
40 },
41 { path: "docs/guide.md", size: 26, content: "# Guide\n\nProject details.", truncated: false },
42 { path: "docs/large.md", size: 600_000, content: null, truncated: true },
43 {
44 path: "assets/logo.png",
45 size: 68,
46 content: null,
47 binaryContent: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wl2V6sAAAAASUVORK5CYII=",
48 mediaType: "image/png",
49 truncated: false,
50 },
51 { path: "src/index.js", size: 22, content: "export const ready = true;", truncated: false },
52 ];
53 const commits = [
54 {
55 sha: headSha,
56 message: `Finish ${repo.name} viewer`,
57 author: "Rasmus",
58 date: "2026-07-22T09:00:00.000Z",
59 additions: 4,
60 deletions: 1,
61 files: [{
62 filename: "src/index.js",
63 status: "modified",
64 additions: 4,
65 deletions: 1,
66 patch: "@@ -1 +1 @@\n-false\n+true",
67 }, {
68 filename: "package-lock.json",
69 status: "modified",
70 additions: 1,
71 deletions: 1,
72 patch: "",
73 }],
74 },
75 {
76 sha: `${repo.id}`.padEnd(40, "b"),
77 message: `Start ${repo.name}`,
78 author: "Rasmus",
79 date: "2026-07-19T09:00:00.000Z",
80 additions: 10,
81 deletions: 0,
82 files: [],
83 },
84 ];
85 return {
86 ...repo,
87 headSha,
88 createdAt: "2026-06-01T10:00:00.000Z",
89 homepage: `https://${repo.name}.test`,
90 topics: ["developer-tools", "snapshot"],
91 license: { name: "MIT License", spdxId: "MIT" },
92 stargazersCount: 12,
93 forksCount: 3,
94 watchersCount: 4,
95 archived: false,
96 languages: [
97 { name: repo.language, bytes: 700, percent: 70 },
98 { name: "HTML", bytes: 300, percent: 30 },
99 ],
100 branches: [
101 { name: repo.defaultBranch, sha: headSha, protected: true },
102 { name: "review-notes", sha: branchSha, protected: false },
103 ],
104 tags: [{ name: "v1.0.0", sha: tagSha }],
105 refSnapshots: [{
106 sha: branchSha,
107 files: [
108 { path: "README.md", size: 33, content: `# ${repo.name}\n\nReview notes branch.`, truncated: false },
109 { path: "src/preview.js", size: 43, content: 'export const branch = "review-notes";', truncated: false },
110 ],
111 commits: [{
112 sha: branchSha,
113 message: `Add ${repo.name} review notes`,
114 author: "Rasmus",
115 date: "2026-07-23T08:00:00.000Z",
116 additions: 7,
117 deletions: 0,
118 files: [{
119 filename: "src/preview.js",
120 status: "added",
121 additions: 7,
122 deletions: 0,
123 patch: '+export const branch = "review-notes";',
124 }],
125 }],
126 }, {
127 sha: tagSha,
128 files: [
129 { path: "README.md", size: 26, content: `# ${repo.name} v1.0.0`, truncated: false },
130 { path: "CHANGELOG.md", size: 24, content: "# Changes\n\nFirst release.", truncated: false },
131 ],
132 commits: [{
133 sha: tagSha,
134 message: `Release ${repo.name} v1.0.0`,
135 author: "Rasmus",
136 date: "2026-07-21T08:00:00.000Z",
137 additions: 12,
138 deletions: 1,
139 files: [],
140 }],
141 }],
142 issues: [{
143 number: 12,
144 title: `Improve ${repo.name} keyboard navigation`,
145 body: "The file browser should be usable without a mouse.",
146 state: "open",
147 stateReason: null,
148 locked: false,
149 author: { login: "reviewer", avatarUrl: "https://avatars.test/reviewer" },
150 labels: [{ name: "accessibility", color: "0969da" }],
151 createdAt: "2026-07-20T08:00:00.000Z",
152 updatedAt: "2026-07-23T07:00:00.000Z",
153 closedAt: null,
154 comments: [{
155 id: 1201,
156 type: "comment",
157 body: "Focus should return to the branch button after closing the menu.",
158 author: { login: "rasmus", avatarUrl: "https://avatars.test/rasmus" },
159 createdAt: "2026-07-21T08:00:00.000Z",
160 updatedAt: "2026-07-21T08:00:00.000Z",
161 state: null,
162 path: null,
163 line: null,
164 }],
165 }],
166 pullRequests: [{
167 number: 14,
168 title: `Add ${repo.name} keyboard shortcuts`,
169 body: "Adds focused navigation for repository reviewers.",
170 state: "open",
171 draft: false,
172 merged: false,
173 mergeableState: "clean",
174 author: { login: "rasmus", avatarUrl: "https://avatars.test/rasmus" },
175 labels: [{ name: "enhancement", color: "1f883d" }],
176 base: "main",
177 head: "keyboard-navigation",
178 createdAt: "2026-07-21T10:00:00.000Z",
179 updatedAt: "2026-07-23T10:00:00.000Z",
180 closedAt: null,
181 mergedAt: null,
182 additions: 8,
183 deletions: 2,
184 changedFiles: 1,
185 commitCount: 2,
186 conversation: [{
187 id: 1401,
188 type: "review",
189 body: "The keyboard flow works as expected.",
190 author: { login: "reviewer", avatarUrl: "https://avatars.test/reviewer" },
191 createdAt: "2026-07-22T10:00:00.000Z",
192 updatedAt: "2026-07-22T10:00:00.000Z",
193 state: "APPROVED",
194 path: null,
195 line: null,
196 }],
197 files: [{
198 filename: "src/index.js",
199 previousFilename: null,
200 status: "modified",
201 additions: 8,
202 deletions: 2,
203 changes: 10,
204 patch: "@@ -1 +1,2 @@\n export const ready = true;\n+export const keyboard = true;",
205 }],
206 }],
207 releases: [{
208 id: repo.id * 10 + 1,
209 tagName: "v1.0.0",
210 targetCommitish: "main",
211 name: `${repo.name} v1.0.0`,
212 body: "## Highlights\n\n- Faster repository reviews\n- Frozen release notes",
213 draft: false,
214 prerelease: false,
215 author: { login: "rasmus", avatarUrl: "https://avatars.test/rasmus" },
216 createdAt: "2026-07-20T10:00:00.000Z",
217 publishedAt: "2026-07-21T10:00:00.000Z",
218 assets: [{
219 id: repo.id * 100 + 1,
220 name: `${repo.name}-v1.0.0.zip`,
221 label: "Source archive",
222 size: 2048,
223 downloadCount: 8,
224 contentType: "application/zip",
225 }],
226 }],
227 files,
228 commits,
229 };
230 }
231
232 export function makeHarness(options = {}) {
233 const store = createStore();
234 let clock = options.now || new Date("2026-07-23T12:00:00.000Z");
235 const calls = { snapshots: [], refreshes: [], repositoryTokens: [], viewerReads: 0 };
236 const github = {
237 authorizationUrl: (state) => `https://github.test/authorize?state=${state}`,
238 installationUrl: (state) => `https://github.test/install?state=${state}`,
239 exchangeCode: async () => ({
240 accessToken: "owner-token",
241 refreshToken: null,
242 expiresAt: null,
243 }),
244 refreshUserToken: async (refreshToken) => {
245 calls.refreshes.push(refreshToken);
246 return {
247 accessToken: "refreshed-token",
248 refreshToken: "next-refresh",
249 expiresAt: "2026-07-24T12:00:00.000Z",
250 };
251 },
252 getViewer: async () => {
253 calls.viewerReads += 1;
254 return {
255 id: 77,
256 login: "rasmus",
257 name: "Rasmus",
258 avatar_url: "https://avatars.test/rasmus",
259 bio: "Building useful software.",
260 };
261 },
262 listRepositories: async (token) => {
263 calls.repositoryTokens.push(token);
264 return repositories;
265 },
266 snapshotRepositories: async (token, selected) => {
267 calls.snapshots.push(selected.map((repo) => repo.id));
268 return selected.map(snapshot);
269 },
270 };
271 const config = {
272 baseUrl: "http://profileshare.test",
273 sessionSecret: "0123456789abcdef0123456789abcdef",
274 github: { clientId: "client", clientSecret: "secret", appSlug: "profileshare" },
275 };
276 const app = createApp({ config, store, github, now: () => new Date(clock) });
277 return {
278 app,
279 agent: request.agent(app),
280 store,
281 github,
282 calls,
283 setNow(value) { clock = new Date(value); },
284 close() { store.close(); },
285 };
286 }
287
288 export async function authenticate(harness) {
289 const start = await harness.agent.get("/auth/github");
290 const state = new URL(start.headers.location).searchParams.get("state");
291 await harness.agent.get(`/auth/github/callback?code=ok&state=${state}`).expect(302);
292 const install = await harness.agent.get("/github/install").expect(302);
293 const installState = new URL(install.headers.location).searchParams.get("state");
294 await harness.agent.get(`/github/installed?installation_id=333&state=${installState}`).expect(302);
295 }
296
297 export async function createShare(harness, repoIds = [101], days = 7) {
298 await authenticate(harness);
299 const response = await harness.agent
300 .post("/shares")
301 .type("form")
302 .send({ repositories: repoIds, days })
303 .expect(201);
304 const match = response.text.match(/http:\/\/profileshare\.test\/s\/([A-Za-z0-9_-]+)/);
305 return { id: match[1], response };
306 }
307