opengstack 0.13.10 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +0 -16
- package/{skills/benchmark/SKILL.md → commands/benchmark.md} +0 -17
- package/{skills/browse/SKILL.md → commands/browse.md} +0 -17
- package/{skills/ship/SKILL.md → commands/canary.md} +0 -18
- package/{skills/careful/SKILL.md → commands/careful.md} +0 -20
- package/{skills/canary/SKILL.md → commands/codex.md} +0 -17
- package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +0 -15
- package/commands/cso.md +72 -0
- package/commands/design-consultation.md +72 -0
- package/commands/design-review.md +72 -0
- package/commands/design-shotgun.md +72 -0
- package/commands/document-release.md +72 -0
- package/{skills/freeze/SKILL.md → commands/freeze.md} +0 -26
- package/{skills/gstack-upgrade/SKILL.md → commands/gstack-upgrade.md} +0 -14
- package/{skills/guard/SKILL.md → commands/guard.md} +0 -31
- package/commands/investigate.md +72 -0
- package/commands/land-and-deploy.md +72 -0
- package/commands/office-hours.md +72 -0
- package/commands/plan-ceo-review.md +72 -0
- package/commands/plan-design-review.md +72 -0
- package/commands/plan-eng-review.md +72 -0
- package/commands/qa-only.md +72 -0
- package/commands/qa.md +72 -0
- package/commands/retro.md +72 -0
- package/commands/review.md +72 -0
- package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +0 -14
- package/commands/setup-deploy.md +72 -0
- package/commands/ship.md +72 -0
- package/{skills/unfreeze/SKILL.md → commands/unfreeze.md} +0 -12
- package/package.json +4 -4
- package/scripts/install-commands.js +45 -0
- package/skills/autoplan/SKILL.md +0 -96
- package/skills/autoplan/SKILL.md.tmpl +0 -694
- package/skills/benchmark/SKILL.md.tmpl +0 -222
- package/skills/browse/SKILL.md.tmpl +0 -131
- package/skills/browse/bin/find-browse +0 -21
- package/skills/browse/bin/remote-slug +0 -14
- package/skills/browse/scripts/build-node-server.sh +0 -48
- package/skills/browse/src/activity.ts +0 -208
- package/skills/browse/src/browser-manager.ts +0 -959
- package/skills/browse/src/buffers.ts +0 -137
- package/skills/browse/src/bun-polyfill.cjs +0 -109
- package/skills/browse/src/cli.ts +0 -678
- package/skills/browse/src/commands.ts +0 -128
- package/skills/browse/src/config.ts +0 -150
- package/skills/browse/src/cookie-import-browser.ts +0 -625
- package/skills/browse/src/cookie-picker-routes.ts +0 -230
- package/skills/browse/src/cookie-picker-ui.ts +0 -688
- package/skills/browse/src/find-browse.ts +0 -61
- package/skills/browse/src/meta-commands.ts +0 -550
- package/skills/browse/src/platform.ts +0 -17
- package/skills/browse/src/read-commands.ts +0 -358
- package/skills/browse/src/server.ts +0 -1192
- package/skills/browse/src/sidebar-agent.ts +0 -280
- package/skills/browse/src/sidebar-utils.ts +0 -21
- package/skills/browse/src/snapshot.ts +0 -407
- package/skills/browse/src/url-validation.ts +0 -95
- package/skills/browse/src/write-commands.ts +0 -364
- package/skills/browse/test/activity.test.ts +0 -120
- package/skills/browse/test/adversarial-security.test.ts +0 -32
- package/skills/browse/test/browser-manager-unit.test.ts +0 -17
- package/skills/browse/test/bun-polyfill.test.ts +0 -72
- package/skills/browse/test/commands.test.ts +0 -2075
- package/skills/browse/test/compare-board.test.ts +0 -342
- package/skills/browse/test/config.test.ts +0 -316
- package/skills/browse/test/cookie-import-browser.test.ts +0 -519
- package/skills/browse/test/cookie-picker-routes.test.ts +0 -260
- package/skills/browse/test/file-drop.test.ts +0 -271
- package/skills/browse/test/find-browse.test.ts +0 -50
- package/skills/browse/test/findport.test.ts +0 -191
- package/skills/browse/test/fixtures/basic.html +0 -33
- package/skills/browse/test/fixtures/cursor-interactive.html +0 -22
- package/skills/browse/test/fixtures/dialog.html +0 -15
- package/skills/browse/test/fixtures/empty.html +0 -2
- package/skills/browse/test/fixtures/forms.html +0 -55
- package/skills/browse/test/fixtures/iframe.html +0 -30
- package/skills/browse/test/fixtures/network-idle.html +0 -30
- package/skills/browse/test/fixtures/qa-eval-checkout.html +0 -108
- package/skills/browse/test/fixtures/qa-eval-spa.html +0 -98
- package/skills/browse/test/fixtures/qa-eval.html +0 -51
- package/skills/browse/test/fixtures/responsive.html +0 -49
- package/skills/browse/test/fixtures/snapshot.html +0 -55
- package/skills/browse/test/fixtures/spa.html +0 -24
- package/skills/browse/test/fixtures/states.html +0 -17
- package/skills/browse/test/fixtures/upload.html +0 -25
- package/skills/browse/test/gstack-config.test.ts +0 -138
- package/skills/browse/test/gstack-update-check.test.ts +0 -514
- package/skills/browse/test/handoff.test.ts +0 -235
- package/skills/browse/test/path-validation.test.ts +0 -91
- package/skills/browse/test/platform.test.ts +0 -37
- package/skills/browse/test/server-auth.test.ts +0 -65
- package/skills/browse/test/sidebar-agent-roundtrip.test.ts +0 -226
- package/skills/browse/test/sidebar-agent.test.ts +0 -199
- package/skills/browse/test/sidebar-integration.test.ts +0 -320
- package/skills/browse/test/sidebar-unit.test.ts +0 -96
- package/skills/browse/test/snapshot.test.ts +0 -467
- package/skills/browse/test/state-ttl.test.ts +0 -35
- package/skills/browse/test/test-server.ts +0 -57
- package/skills/browse/test/url-validation.test.ts +0 -72
- package/skills/browse/test/watch.test.ts +0 -129
- package/skills/canary/SKILL.md.tmpl +0 -212
- package/skills/careful/SKILL.md.tmpl +0 -56
- package/skills/careful/bin/check-careful.sh +0 -112
- package/skills/codex/SKILL.md +0 -90
- package/skills/codex/SKILL.md.tmpl +0 -417
- package/skills/connect-chrome/SKILL.md.tmpl +0 -195
- package/skills/cso/ACKNOWLEDGEMENTS.md +0 -14
- package/skills/cso/SKILL.md +0 -93
- package/skills/cso/SKILL.md.tmpl +0 -606
- package/skills/design-consultation/SKILL.md +0 -94
- package/skills/design-consultation/SKILL.md.tmpl +0 -415
- package/skills/design-review/SKILL.md +0 -94
- package/skills/design-review/SKILL.md.tmpl +0 -290
- package/skills/design-shotgun/SKILL.md +0 -91
- package/skills/design-shotgun/SKILL.md.tmpl +0 -285
- package/skills/document-release/SKILL.md +0 -91
- package/skills/document-release/SKILL.md.tmpl +0 -359
- package/skills/freeze/SKILL.md.tmpl +0 -77
- package/skills/freeze/bin/check-freeze.sh +0 -79
- package/skills/gstack-upgrade/SKILL.md.tmpl +0 -222
- package/skills/guard/SKILL.md.tmpl +0 -77
- package/skills/investigate/SKILL.md +0 -105
- package/skills/investigate/SKILL.md.tmpl +0 -194
- package/skills/land-and-deploy/SKILL.md.tmpl +0 -881
- package/skills/office-hours/SKILL.md +0 -96
- package/skills/office-hours/SKILL.md.tmpl +0 -645
- package/skills/plan-ceo-review/SKILL.md +0 -94
- package/skills/plan-ceo-review/SKILL.md.tmpl +0 -811
- package/skills/plan-design-review/SKILL.md +0 -92
- package/skills/plan-design-review/SKILL.md.tmpl +0 -446
- package/skills/plan-eng-review/SKILL.md +0 -93
- package/skills/plan-eng-review/SKILL.md.tmpl +0 -303
- package/skills/qa/SKILL.md +0 -95
- package/skills/qa/SKILL.md.tmpl +0 -316
- package/skills/qa/references/issue-taxonomy.md +0 -85
- package/skills/qa/templates/qa-report-template.md +0 -126
- package/skills/qa-only/SKILL.md +0 -89
- package/skills/qa-only/SKILL.md.tmpl +0 -101
- package/skills/retro/SKILL.md +0 -89
- package/skills/retro/SKILL.md.tmpl +0 -820
- package/skills/review/SKILL.md +0 -92
- package/skills/review/SKILL.md.tmpl +0 -281
- package/skills/review/TODOS-format.md +0 -62
- package/skills/review/checklist.md +0 -220
- package/skills/review/design-checklist.md +0 -132
- package/skills/review/greptile-triage.md +0 -220
- package/skills/setup-browser-cookies/SKILL.md.tmpl +0 -81
- package/skills/setup-deploy/SKILL.md +0 -92
- package/skills/setup-deploy/SKILL.md.tmpl +0 -215
- package/skills/ship/SKILL.md.tmpl +0 -636
- package/skills/unfreeze/SKILL.md.tmpl +0 -36
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for cookie-picker route handler
|
|
3
|
-
*
|
|
4
|
-
* Tests the HTTP glue layer directly with mock BrowserManager objects.
|
|
5
|
-
* Verifies that all routes return valid JSON (not HTML) with correct CORS headers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect } from 'bun:test';
|
|
9
|
-
import { handleCookiePickerRoute } from '../src/cookie-picker-routes';
|
|
10
|
-
|
|
11
|
-
// ─── Mock BrowserManager ──────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
function mockBrowserManager() {
|
|
14
|
-
const addedCookies: any[] = [];
|
|
15
|
-
const clearedDomains: string[] = [];
|
|
16
|
-
return {
|
|
17
|
-
bm: {
|
|
18
|
-
getPage: () => ({
|
|
19
|
-
context: () => ({
|
|
20
|
-
addCookies: (cookies: any[]) => { addedCookies.push(...cookies); },
|
|
21
|
-
clearCookies: (opts: { domain: string }) => { clearedDomains.push(opts.domain); },
|
|
22
|
-
}),
|
|
23
|
-
}),
|
|
24
|
-
} as any,
|
|
25
|
-
addedCookies,
|
|
26
|
-
clearedDomains,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function makeUrl(path: string, port = 9470): URL {
|
|
31
|
-
return new URL(`http://127.0.0.1:${port}${path}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function makeReq(method: string, body?: any): Request {
|
|
35
|
-
const opts: RequestInit = { method };
|
|
36
|
-
if (body) {
|
|
37
|
-
opts.body = JSON.stringify(body);
|
|
38
|
-
opts.headers = { 'Content-Type': 'application/json' };
|
|
39
|
-
}
|
|
40
|
-
return new Request('http://127.0.0.1:9470', opts);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ─── Tests ──────────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
describe('cookie-picker-routes', () => {
|
|
46
|
-
describe('CORS', () => {
|
|
47
|
-
test('OPTIONS returns 204 with correct CORS headers', async () => {
|
|
48
|
-
const { bm } = mockBrowserManager();
|
|
49
|
-
const url = makeUrl('/cookie-picker/browsers');
|
|
50
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'OPTIONS' });
|
|
51
|
-
|
|
52
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
53
|
-
|
|
54
|
-
expect(res.status).toBe(204);
|
|
55
|
-
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9470');
|
|
56
|
-
expect(res.headers.get('Access-Control-Allow-Methods')).toContain('POST');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('JSON responses include correct CORS origin with port', async () => {
|
|
60
|
-
const { bm } = mockBrowserManager();
|
|
61
|
-
const url = makeUrl('/cookie-picker/browsers', 9450);
|
|
62
|
-
const req = new Request('http://127.0.0.1:9450', { method: 'GET' });
|
|
63
|
-
|
|
64
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
65
|
-
|
|
66
|
-
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://127.0.0.1:9450');
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('JSON responses (not HTML)', () => {
|
|
71
|
-
test('GET /cookie-picker/browsers returns JSON', async () => {
|
|
72
|
-
const { bm } = mockBrowserManager();
|
|
73
|
-
const url = makeUrl('/cookie-picker/browsers');
|
|
74
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
75
|
-
|
|
76
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
77
|
-
|
|
78
|
-
expect(res.status).toBe(200);
|
|
79
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
80
|
-
const body = await res.json();
|
|
81
|
-
expect(body).toHaveProperty('browsers');
|
|
82
|
-
expect(Array.isArray(body.browsers)).toBe(true);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('GET /cookie-picker/domains without browser param returns JSON error', async () => {
|
|
86
|
-
const { bm } = mockBrowserManager();
|
|
87
|
-
const url = makeUrl('/cookie-picker/domains');
|
|
88
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
89
|
-
|
|
90
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
91
|
-
|
|
92
|
-
expect(res.status).toBe(400);
|
|
93
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
94
|
-
const body = await res.json();
|
|
95
|
-
expect(body).toHaveProperty('error');
|
|
96
|
-
expect(body).toHaveProperty('code', 'missing_param');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('POST /cookie-picker/import with invalid JSON returns JSON error', async () => {
|
|
100
|
-
const { bm } = mockBrowserManager();
|
|
101
|
-
const url = makeUrl('/cookie-picker/import');
|
|
102
|
-
const req = new Request('http://127.0.0.1:9470', {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
body: 'not json',
|
|
105
|
-
headers: { 'Content-Type': 'application/json' },
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
109
|
-
|
|
110
|
-
expect(res.status).toBe(400);
|
|
111
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
112
|
-
const body = await res.json();
|
|
113
|
-
expect(body.code).toBe('bad_request');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('POST /cookie-picker/import missing browser field returns JSON error', async () => {
|
|
117
|
-
const { bm } = mockBrowserManager();
|
|
118
|
-
const url = makeUrl('/cookie-picker/import');
|
|
119
|
-
const req = makeReq('POST', { domains: ['.example.com'] });
|
|
120
|
-
|
|
121
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
122
|
-
|
|
123
|
-
expect(res.status).toBe(400);
|
|
124
|
-
const body = await res.json();
|
|
125
|
-
expect(body.code).toBe('missing_param');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('POST /cookie-picker/import missing domains returns JSON error', async () => {
|
|
129
|
-
const { bm } = mockBrowserManager();
|
|
130
|
-
const url = makeUrl('/cookie-picker/import');
|
|
131
|
-
const req = makeReq('POST', { browser: 'Chrome' });
|
|
132
|
-
|
|
133
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
134
|
-
|
|
135
|
-
expect(res.status).toBe(400);
|
|
136
|
-
const body = await res.json();
|
|
137
|
-
expect(body.code).toBe('missing_param');
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('POST /cookie-picker/remove with invalid JSON returns JSON error', async () => {
|
|
141
|
-
const { bm } = mockBrowserManager();
|
|
142
|
-
const url = makeUrl('/cookie-picker/remove');
|
|
143
|
-
const req = new Request('http://127.0.0.1:9470', {
|
|
144
|
-
method: 'POST',
|
|
145
|
-
body: '{bad',
|
|
146
|
-
headers: { 'Content-Type': 'application/json' },
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
150
|
-
|
|
151
|
-
expect(res.status).toBe(400);
|
|
152
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('POST /cookie-picker/remove missing domains returns JSON error', async () => {
|
|
156
|
-
const { bm } = mockBrowserManager();
|
|
157
|
-
const url = makeUrl('/cookie-picker/remove');
|
|
158
|
-
const req = makeReq('POST', {});
|
|
159
|
-
|
|
160
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
161
|
-
|
|
162
|
-
expect(res.status).toBe(400);
|
|
163
|
-
const body = await res.json();
|
|
164
|
-
expect(body.code).toBe('missing_param');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('GET /cookie-picker/imported returns JSON with domain list', async () => {
|
|
168
|
-
const { bm } = mockBrowserManager();
|
|
169
|
-
const url = makeUrl('/cookie-picker/imported');
|
|
170
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
171
|
-
|
|
172
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
173
|
-
|
|
174
|
-
expect(res.status).toBe(200);
|
|
175
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
176
|
-
const body = await res.json();
|
|
177
|
-
expect(body).toHaveProperty('domains');
|
|
178
|
-
expect(body).toHaveProperty('totalDomains');
|
|
179
|
-
expect(body).toHaveProperty('totalCookies');
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe('routing', () => {
|
|
184
|
-
test('GET /cookie-picker returns HTML', async () => {
|
|
185
|
-
const { bm } = mockBrowserManager();
|
|
186
|
-
const url = makeUrl('/cookie-picker');
|
|
187
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
188
|
-
|
|
189
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
190
|
-
|
|
191
|
-
expect(res.status).toBe(200);
|
|
192
|
-
expect(res.headers.get('Content-Type')).toContain('text/html');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test('unknown path returns 404', async () => {
|
|
196
|
-
const { bm } = mockBrowserManager();
|
|
197
|
-
const url = makeUrl('/cookie-picker/nonexistent');
|
|
198
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
199
|
-
|
|
200
|
-
const res = await handleCookiePickerRoute(url, req, bm);
|
|
201
|
-
|
|
202
|
-
expect(res.status).toBe(404);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe('auth gate security', () => {
|
|
207
|
-
test('GET /cookie-picker HTML page works without auth token', async () => {
|
|
208
|
-
const { bm } = mockBrowserManager();
|
|
209
|
-
const url = makeUrl('/cookie-picker');
|
|
210
|
-
// Request with no Authorization header, but authToken is set on the server
|
|
211
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
212
|
-
|
|
213
|
-
const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
|
|
214
|
-
|
|
215
|
-
expect(res.status).toBe(200);
|
|
216
|
-
expect(res.headers.get('Content-Type')).toContain('text/html');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('GET /cookie-picker/browsers returns 401 without auth', async () => {
|
|
220
|
-
const { bm } = mockBrowserManager();
|
|
221
|
-
const url = makeUrl('/cookie-picker/browsers');
|
|
222
|
-
// No Authorization header
|
|
223
|
-
const req = new Request('http://127.0.0.1:9470', { method: 'GET' });
|
|
224
|
-
|
|
225
|
-
const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
|
|
226
|
-
|
|
227
|
-
expect(res.status).toBe(401);
|
|
228
|
-
const body = await res.json();
|
|
229
|
-
expect(body.error).toBe('Unauthorized');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('POST /cookie-picker/import returns 401 without auth', async () => {
|
|
233
|
-
const { bm } = mockBrowserManager();
|
|
234
|
-
const url = makeUrl('/cookie-picker/import');
|
|
235
|
-
const req = makeReq('POST', { browser: 'Chrome', domains: ['.example.com'] });
|
|
236
|
-
|
|
237
|
-
const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
|
|
238
|
-
|
|
239
|
-
expect(res.status).toBe(401);
|
|
240
|
-
const body = await res.json();
|
|
241
|
-
expect(body.error).toBe('Unauthorized');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('GET /cookie-picker/browsers works with valid auth', async () => {
|
|
245
|
-
const { bm } = mockBrowserManager();
|
|
246
|
-
const url = makeUrl('/cookie-picker/browsers');
|
|
247
|
-
const req = new Request('http://127.0.0.1:9470', {
|
|
248
|
-
method: 'GET',
|
|
249
|
-
headers: { 'Authorization': 'Bearer test-secret-token' },
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const res = await handleCookiePickerRoute(url, req, bm, 'test-secret-token');
|
|
253
|
-
|
|
254
|
-
expect(res.status).toBe(200);
|
|
255
|
-
expect(res.headers.get('Content-Type')).toBe('application/json');
|
|
256
|
-
const body = await res.json();
|
|
257
|
-
expect(body).toHaveProperty('browsers');
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the inbox meta-command handler (file drop relay).
|
|
3
|
-
*
|
|
4
|
-
* Tests the inbox display, --clear flag, and edge cases by creating
|
|
5
|
-
* temp directories with test JSON files and calling handleMetaCommand.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import * as os from 'os';
|
|
12
|
-
import { handleMetaCommand } from '../src/meta-commands';
|
|
13
|
-
import { BrowserManager } from '../src/browser-manager';
|
|
14
|
-
|
|
15
|
-
let tmpDir: string;
|
|
16
|
-
let bm: BrowserManager;
|
|
17
|
-
|
|
18
|
-
// We need a BrowserManager instance for handleMetaCommand, but inbox
|
|
19
|
-
// doesn't use it. We also need to mock git rev-parse to point to our
|
|
20
|
-
// temp directory. We'll test the inbox logic directly by manipulating
|
|
21
|
-
// the filesystem and using child_process.execSync override.
|
|
22
|
-
|
|
23
|
-
// ─── Direct filesystem tests (bypassing handleMetaCommand) ──────
|
|
24
|
-
// The inbox handler in meta-commands.ts calls `git rev-parse --show-toplevel`
|
|
25
|
-
// to find the inbox directory. Since we can't easily mock that in unit tests,
|
|
26
|
-
// we test the inbox parsing logic directly.
|
|
27
|
-
|
|
28
|
-
interface InboxMessage {
|
|
29
|
-
timestamp: string;
|
|
30
|
-
url: string;
|
|
31
|
-
userMessage: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Replicate the inbox file reading logic from meta-commands.ts */
|
|
35
|
-
function readInbox(inboxDir: string): InboxMessage[] {
|
|
36
|
-
if (!fs.existsSync(inboxDir)) return [];
|
|
37
|
-
|
|
38
|
-
const files = fs.readdirSync(inboxDir)
|
|
39
|
-
.filter(f => f.endsWith('.json') && !f.startsWith('.'))
|
|
40
|
-
.sort()
|
|
41
|
-
.reverse();
|
|
42
|
-
|
|
43
|
-
if (files.length === 0) return [];
|
|
44
|
-
|
|
45
|
-
const messages: InboxMessage[] = [];
|
|
46
|
-
for (const file of files) {
|
|
47
|
-
try {
|
|
48
|
-
const data = JSON.parse(fs.readFileSync(path.join(inboxDir, file), 'utf-8'));
|
|
49
|
-
messages.push({
|
|
50
|
-
timestamp: data.timestamp || '',
|
|
51
|
-
url: data.page?.url || 'unknown',
|
|
52
|
-
userMessage: data.userMessage || '',
|
|
53
|
-
});
|
|
54
|
-
} catch {
|
|
55
|
-
// Skip malformed files
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return messages;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Replicate the inbox formatting logic from meta-commands.ts */
|
|
62
|
-
function formatInbox(messages: InboxMessage[]): string {
|
|
63
|
-
if (messages.length === 0) return 'Inbox empty.';
|
|
64
|
-
|
|
65
|
-
const lines: string[] = [];
|
|
66
|
-
lines.push(`SIDEBAR INBOX (${messages.length} message${messages.length === 1 ? '' : 's'})`);
|
|
67
|
-
lines.push('────────────────────────────────');
|
|
68
|
-
|
|
69
|
-
for (const msg of messages) {
|
|
70
|
-
const ts = msg.timestamp ? `[${msg.timestamp}]` : '[unknown]';
|
|
71
|
-
lines.push(`${ts} ${msg.url}`);
|
|
72
|
-
lines.push(` "${msg.userMessage}"`);
|
|
73
|
-
lines.push('');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
lines.push('────────────────────────────────');
|
|
77
|
-
return lines.join('\n');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Replicate the --clear logic from meta-commands.ts */
|
|
81
|
-
function clearInbox(inboxDir: string): number {
|
|
82
|
-
const files = fs.readdirSync(inboxDir)
|
|
83
|
-
.filter(f => f.endsWith('.json') && !f.startsWith('.'));
|
|
84
|
-
for (const file of files) {
|
|
85
|
-
try { fs.unlinkSync(path.join(inboxDir, file)); } catch {}
|
|
86
|
-
}
|
|
87
|
-
return files.length;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function writeTestInboxFile(
|
|
91
|
-
inboxDir: string,
|
|
92
|
-
message: string,
|
|
93
|
-
pageUrl: string,
|
|
94
|
-
timestamp: string,
|
|
95
|
-
): string {
|
|
96
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
97
|
-
const filename = `${timestamp.replace(/:/g, '-')}-observation.json`;
|
|
98
|
-
const filePath = path.join(inboxDir, filename);
|
|
99
|
-
fs.writeFileSync(filePath, JSON.stringify({
|
|
100
|
-
type: 'observation',
|
|
101
|
-
timestamp,
|
|
102
|
-
page: { url: pageUrl, title: '' },
|
|
103
|
-
userMessage: message,
|
|
104
|
-
sidebarSessionId: 'test-session',
|
|
105
|
-
}, null, 2));
|
|
106
|
-
return filePath;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
beforeEach(() => {
|
|
110
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'file-drop-test-'));
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
afterEach(() => {
|
|
114
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ─── Empty Inbox ─────────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
describe('inbox — empty states', () => {
|
|
120
|
-
test('no .context/sidebar-inbox directory returns empty', () => {
|
|
121
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
122
|
-
const messages = readInbox(inboxDir);
|
|
123
|
-
expect(messages.length).toBe(0);
|
|
124
|
-
expect(formatInbox(messages)).toBe('Inbox empty.');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('empty inbox directory returns empty', () => {
|
|
128
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
129
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
130
|
-
const messages = readInbox(inboxDir);
|
|
131
|
-
expect(messages.length).toBe(0);
|
|
132
|
-
expect(formatInbox(messages)).toBe('Inbox empty.');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('directory with only dotfiles returns empty', () => {
|
|
136
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
137
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
138
|
-
fs.writeFileSync(path.join(inboxDir, '.tmp-file.json'), '{}');
|
|
139
|
-
const messages = readInbox(inboxDir);
|
|
140
|
-
expect(messages.length).toBe(0);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// ─── Valid Messages ──────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
describe('inbox — valid messages', () => {
|
|
147
|
-
test('displays formatted output with timestamps and URLs', () => {
|
|
148
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
149
|
-
writeTestInboxFile(inboxDir, 'This button is broken', 'https://example.com/page', '2024-06-15T10:30:00.000Z');
|
|
150
|
-
writeTestInboxFile(inboxDir, 'Login form fails', 'https://example.com/login', '2024-06-15T10:31:00.000Z');
|
|
151
|
-
|
|
152
|
-
const messages = readInbox(inboxDir);
|
|
153
|
-
expect(messages.length).toBe(2);
|
|
154
|
-
|
|
155
|
-
const output = formatInbox(messages);
|
|
156
|
-
expect(output).toContain('SIDEBAR INBOX (2 messages)');
|
|
157
|
-
expect(output).toContain('https://example.com/page');
|
|
158
|
-
expect(output).toContain('https://example.com/login');
|
|
159
|
-
expect(output).toContain('"This button is broken"');
|
|
160
|
-
expect(output).toContain('"Login form fails"');
|
|
161
|
-
expect(output).toContain('[2024-06-15T10:30:00.000Z]');
|
|
162
|
-
expect(output).toContain('[2024-06-15T10:31:00.000Z]');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('single message uses singular form', () => {
|
|
166
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
167
|
-
writeTestInboxFile(inboxDir, 'Just one', 'https://example.com', '2024-06-15T10:30:00.000Z');
|
|
168
|
-
|
|
169
|
-
const messages = readInbox(inboxDir);
|
|
170
|
-
const output = formatInbox(messages);
|
|
171
|
-
expect(output).toContain('1 message)');
|
|
172
|
-
expect(output).not.toContain('messages)');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('messages sorted newest first', () => {
|
|
176
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
177
|
-
writeTestInboxFile(inboxDir, 'older', 'https://example.com', '2024-06-15T10:00:00.000Z');
|
|
178
|
-
writeTestInboxFile(inboxDir, 'newer', 'https://example.com', '2024-06-15T11:00:00.000Z');
|
|
179
|
-
|
|
180
|
-
const messages = readInbox(inboxDir);
|
|
181
|
-
// Filenames sort lexicographically, reversed = newest first
|
|
182
|
-
expect(messages[0].userMessage).toBe('newer');
|
|
183
|
-
expect(messages[1].userMessage).toBe('older');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ─── Malformed Files ─────────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
describe('inbox — malformed files', () => {
|
|
190
|
-
test('malformed JSON files are skipped gracefully', () => {
|
|
191
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
192
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
193
|
-
|
|
194
|
-
// Write a valid message
|
|
195
|
-
writeTestInboxFile(inboxDir, 'valid message', 'https://example.com', '2024-06-15T10:30:00.000Z');
|
|
196
|
-
|
|
197
|
-
// Write a malformed JSON file
|
|
198
|
-
fs.writeFileSync(
|
|
199
|
-
path.join(inboxDir, '2024-06-15T10-35-00.000Z-observation.json'),
|
|
200
|
-
'this is not valid json {{{',
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const messages = readInbox(inboxDir);
|
|
204
|
-
expect(messages.length).toBe(1);
|
|
205
|
-
expect(messages[0].userMessage).toBe('valid message');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('JSON file missing fields uses defaults', () => {
|
|
209
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
210
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
211
|
-
|
|
212
|
-
// Write a JSON file with missing fields
|
|
213
|
-
fs.writeFileSync(
|
|
214
|
-
path.join(inboxDir, '2024-06-15T10-30-00.000Z-observation.json'),
|
|
215
|
-
JSON.stringify({ type: 'observation' }),
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
const messages = readInbox(inboxDir);
|
|
219
|
-
expect(messages.length).toBe(1);
|
|
220
|
-
expect(messages[0].timestamp).toBe('');
|
|
221
|
-
expect(messages[0].url).toBe('unknown');
|
|
222
|
-
expect(messages[0].userMessage).toBe('');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ─── Clear Flag ──────────────────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
describe('inbox — --clear flag', () => {
|
|
229
|
-
test('files deleted after clear', () => {
|
|
230
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
231
|
-
writeTestInboxFile(inboxDir, 'message 1', 'https://example.com', '2024-06-15T10:30:00.000Z');
|
|
232
|
-
writeTestInboxFile(inboxDir, 'message 2', 'https://example.com', '2024-06-15T10:31:00.000Z');
|
|
233
|
-
|
|
234
|
-
// Verify files exist
|
|
235
|
-
const filesBefore = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
|
|
236
|
-
expect(filesBefore.length).toBe(2);
|
|
237
|
-
|
|
238
|
-
// Clear
|
|
239
|
-
const cleared = clearInbox(inboxDir);
|
|
240
|
-
expect(cleared).toBe(2);
|
|
241
|
-
|
|
242
|
-
// Verify files deleted
|
|
243
|
-
const filesAfter = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
|
|
244
|
-
expect(filesAfter.length).toBe(0);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test('clear on empty directory does nothing', () => {
|
|
248
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
249
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
250
|
-
|
|
251
|
-
const cleared = clearInbox(inboxDir);
|
|
252
|
-
expect(cleared).toBe(0);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('clear preserves dotfiles', () => {
|
|
256
|
-
const inboxDir = path.join(tmpDir, '.context', 'sidebar-inbox');
|
|
257
|
-
fs.mkdirSync(inboxDir, { recursive: true });
|
|
258
|
-
|
|
259
|
-
// Write a dotfile and a regular file
|
|
260
|
-
fs.writeFileSync(path.join(inboxDir, '.keep'), '');
|
|
261
|
-
writeTestInboxFile(inboxDir, 'to be cleared', 'https://example.com', '2024-06-15T10:30:00.000Z');
|
|
262
|
-
|
|
263
|
-
clearInbox(inboxDir);
|
|
264
|
-
|
|
265
|
-
// Dotfile should remain
|
|
266
|
-
expect(fs.existsSync(path.join(inboxDir, '.keep'))).toBe(true);
|
|
267
|
-
// Regular file should be gone
|
|
268
|
-
const jsonFiles = fs.readdirSync(inboxDir).filter(f => f.endsWith('.json') && !f.startsWith('.'));
|
|
269
|
-
expect(jsonFiles.length).toBe(0);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for find-browse binary locator.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, expect } from 'bun:test';
|
|
6
|
-
import { locateBinary } from '../src/find-browse';
|
|
7
|
-
import { existsSync } from 'fs';
|
|
8
|
-
|
|
9
|
-
describe('locateBinary', () => {
|
|
10
|
-
test('returns null when no binary exists at known paths', () => {
|
|
11
|
-
// This test depends on the test environment — if a real binary exists at
|
|
12
|
-
// ~/.claude/skills/gstack/browse/dist/browse, it will find it.
|
|
13
|
-
// We mainly test that the function doesn't throw.
|
|
14
|
-
const result = locateBinary();
|
|
15
|
-
expect(result === null || typeof result === 'string').toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('returns string path when binary exists', () => {
|
|
19
|
-
const result = locateBinary();
|
|
20
|
-
if (result !== null) {
|
|
21
|
-
expect(existsSync(result)).toBe(true);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('priority chain checks .codex, .agents, .claude markers', () => {
|
|
26
|
-
// Verify the source code implements the correct priority order.
|
|
27
|
-
// We read the function source to confirm the markers array order.
|
|
28
|
-
const src = require('fs').readFileSync(require('path').join(__dirname, '../src/find-browse.ts'), 'utf-8');
|
|
29
|
-
// The markers array should list .codex first, then .agents, then .claude
|
|
30
|
-
const markersMatch = src.match(/const markers = \[([^\]]+)\]/);
|
|
31
|
-
expect(markersMatch).not.toBeNull();
|
|
32
|
-
const markers = markersMatch![1];
|
|
33
|
-
const codexIdx = markers.indexOf('.codex');
|
|
34
|
-
const agentsIdx = markers.indexOf('.agents');
|
|
35
|
-
const claudeIdx = markers.indexOf('.claude');
|
|
36
|
-
// All three must be present
|
|
37
|
-
expect(codexIdx).toBeGreaterThanOrEqual(0);
|
|
38
|
-
expect(agentsIdx).toBeGreaterThanOrEqual(0);
|
|
39
|
-
expect(claudeIdx).toBeGreaterThanOrEqual(0);
|
|
40
|
-
// .codex before .agents before .claude
|
|
41
|
-
expect(codexIdx).toBeLessThan(agentsIdx);
|
|
42
|
-
expect(agentsIdx).toBeLessThan(claudeIdx);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('function signature accepts no arguments', () => {
|
|
46
|
-
// locateBinary should be callable with no arguments
|
|
47
|
-
expect(typeof locateBinary).toBe('function');
|
|
48
|
-
expect(locateBinary.length).toBe(0);
|
|
49
|
-
});
|
|
50
|
-
});
|