opengstack 0.13.10 → 0.14.2
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/AGENTS.md +4 -4
- package/CLAUDE.md +127 -110
- package/README.md +10 -5
- package/SKILL.md +500 -70
- package/bin/opengstack.js +69 -69
- package/{skills/land-and-deploy/SKILL.md → commands/autoplan.md} +7 -25
- package/{skills/benchmark/SKILL.md → commands/benchmark.md} +84 -108
- package/{skills/browse/SKILL.md → commands/browse.md} +60 -81
- package/{skills/ship/SKILL.md → commands/canary.md} +7 -27
- package/{skills/careful/SKILL.md → commands/careful.md} +2 -22
- package/{skills/canary/SKILL.md → commands/codex.md} +7 -26
- package/{skills/connect-chrome/SKILL.md → commands/connect-chrome.md} +7 -24
- package/commands/cso.md +70 -0
- package/commands/design-consultation.md +70 -0
- package/commands/design-review.md +70 -0
- package/commands/design-shotgun.md +70 -0
- package/commands/document-release.md +70 -0
- package/{skills/freeze/SKILL.md → commands/freeze.md} +3 -29
- package/{skills/guard/SKILL.md → commands/guard.md} +4 -35
- package/commands/investigate.md +70 -0
- package/commands/land-and-deploy.md +70 -0
- package/commands/office-hours.md +70 -0
- package/{skills/gstack-upgrade/SKILL.md → commands/opengstack-upgrade.md} +64 -79
- package/commands/plan-ceo-review.md +70 -0
- package/commands/plan-design-review.md +70 -0
- package/commands/plan-eng-review.md +70 -0
- package/commands/qa-only.md +70 -0
- package/commands/qa.md +70 -0
- package/commands/retro.md +70 -0
- package/commands/review.md +70 -0
- package/{skills/setup-browser-cookies/SKILL.md → commands/setup-browser-cookies.md} +22 -40
- package/commands/setup-deploy.md +70 -0
- package/commands/ship.md +70 -0
- package/commands/unfreeze.md +25 -0
- package/docs/designs/CHROME_VS_CHROMIUM_EXPLORATION.md +9 -9
- package/docs/designs/CONDUCTOR_CHROME_SIDEBAR_INTEGRATION.md +2 -2
- package/docs/designs/CONDUCTOR_SESSION_API.md +16 -16
- package/docs/designs/DESIGN_SHOTGUN.md +74 -74
- package/docs/designs/DESIGN_TOOLS_V1.md +111 -111
- package/docs/skills.md +483 -202
- package/package.json +42 -43
- package/scripts/analytics.ts +188 -0
- package/scripts/dev-skill.ts +83 -0
- package/scripts/discover-skills.ts +39 -0
- package/scripts/eval-compare.ts +97 -0
- package/scripts/eval-list.ts +117 -0
- package/scripts/eval-select.ts +86 -0
- package/scripts/eval-summary.ts +188 -0
- package/scripts/eval-watch.ts +172 -0
- package/scripts/gen-skill-docs.ts +473 -0
- package/scripts/resolvers/browse.ts +129 -0
- package/scripts/resolvers/codex-helpers.ts +133 -0
- package/scripts/resolvers/composition.ts +48 -0
- package/scripts/resolvers/confidence.ts +37 -0
- package/scripts/resolvers/constants.ts +50 -0
- package/scripts/resolvers/design.ts +950 -0
- package/scripts/resolvers/index.ts +59 -0
- package/scripts/resolvers/learnings.ts +96 -0
- package/scripts/resolvers/preamble.ts +505 -0
- package/scripts/resolvers/review.ts +884 -0
- package/scripts/resolvers/testing.ts +573 -0
- package/scripts/resolvers/types.ts +45 -0
- package/scripts/resolvers/utility.ts +421 -0
- package/scripts/skill-check.ts +190 -0
- package/scripts/cleanup.py +0 -100
- package/scripts/filter-skills.sh +0 -114
- package/scripts/filter_skills.py +0 -164
- package/scripts/install-skills.js +0 -60
- 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 +0 -37
- package/skills/unfreeze/SKILL.md.tmpl +0 -36
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Snapshot command tests
|
|
3
|
-
*
|
|
4
|
-
* Tests: accessibility tree snapshots, ref-based element selection,
|
|
5
|
-
* ref invalidation on navigation, and ref resolution in commands.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
9
|
-
import { startTestServer } from './test-server';
|
|
10
|
-
import { BrowserManager } from '../src/browser-manager';
|
|
11
|
-
import { handleReadCommand } from '../src/read-commands';
|
|
12
|
-
import { handleWriteCommand } from '../src/write-commands';
|
|
13
|
-
import { handleMetaCommand } from '../src/meta-commands';
|
|
14
|
-
import * as fs from 'fs';
|
|
15
|
-
|
|
16
|
-
let testServer: ReturnType<typeof startTestServer>;
|
|
17
|
-
let bm: BrowserManager;
|
|
18
|
-
let baseUrl: string;
|
|
19
|
-
const shutdown = async () => {};
|
|
20
|
-
|
|
21
|
-
beforeAll(async () => {
|
|
22
|
-
testServer = startTestServer(0);
|
|
23
|
-
baseUrl = testServer.url;
|
|
24
|
-
|
|
25
|
-
bm = new BrowserManager();
|
|
26
|
-
await bm.launch();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterAll(() => {
|
|
30
|
-
try { testServer.server.stop(); } catch {}
|
|
31
|
-
setTimeout(() => process.exit(0), 500);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// ─── Snapshot Output ────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
describe('Snapshot', () => {
|
|
37
|
-
test('snapshot returns accessibility tree with refs', async () => {
|
|
38
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
39
|
-
const result = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
40
|
-
expect(result).toContain('@e');
|
|
41
|
-
expect(result).toContain('[heading]');
|
|
42
|
-
expect(result).toContain('"Snapshot Test"');
|
|
43
|
-
expect(result).toContain('[button]');
|
|
44
|
-
expect(result).toContain('[link]');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('snapshot -i returns only interactive elements', async () => {
|
|
48
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
49
|
-
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
50
|
-
expect(result).toContain('[button]');
|
|
51
|
-
expect(result).toContain('[link]');
|
|
52
|
-
expect(result).toContain('[textbox]');
|
|
53
|
-
// Should NOT contain non-interactive roles like heading or paragraph
|
|
54
|
-
expect(result).not.toContain('[heading]');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('snapshot -c returns compact output', async () => {
|
|
58
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
59
|
-
const full = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
60
|
-
const compact = await handleMetaCommand('snapshot', ['-c'], bm, shutdown);
|
|
61
|
-
// Compact should have fewer lines (empty structural elements removed)
|
|
62
|
-
const fullLines = full.split('\n').length;
|
|
63
|
-
const compactLines = compact.split('\n').length;
|
|
64
|
-
expect(compactLines).toBeLessThanOrEqual(fullLines);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('snapshot -d 2 limits depth', async () => {
|
|
68
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
69
|
-
const shallow = await handleMetaCommand('snapshot', ['-d', '2'], bm, shutdown);
|
|
70
|
-
const deep = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
71
|
-
// Shallow should have fewer or equal lines
|
|
72
|
-
expect(shallow.split('\n').length).toBeLessThanOrEqual(deep.split('\n').length);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('snapshot -s "#main" scopes to selector', async () => {
|
|
76
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
77
|
-
const scoped = await handleMetaCommand('snapshot', ['-s', '#main'], bm, shutdown);
|
|
78
|
-
// Should contain elements inside #main
|
|
79
|
-
expect(scoped).toContain('[button]');
|
|
80
|
-
expect(scoped).toContain('"Submit"');
|
|
81
|
-
// Should NOT contain elements outside #main (like nav links)
|
|
82
|
-
expect(scoped).not.toContain('"Internal Link"');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('snapshot on page with no interactive elements', async () => {
|
|
86
|
-
// Navigate to about:blank which has minimal content
|
|
87
|
-
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
|
88
|
-
const result = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
89
|
-
// basic.html has links, so this should find those
|
|
90
|
-
expect(result).toContain('[link]');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('second snapshot generates fresh refs', async () => {
|
|
94
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
95
|
-
const snap1 = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
96
|
-
const snap2 = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
97
|
-
// Both should have @e1 (refs restart from 1)
|
|
98
|
-
expect(snap1).toContain('@e1');
|
|
99
|
-
expect(snap2).toContain('@e1');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// ─── Ref-Based Interaction ──────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
describe('Ref resolution', () => {
|
|
106
|
-
test('click @ref works after snapshot', async () => {
|
|
107
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
108
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
109
|
-
// Find a button ref
|
|
110
|
-
const buttonLine = snap.split('\n').find(l => l.includes('[button]') && l.includes('"Submit"'));
|
|
111
|
-
expect(buttonLine).toBeDefined();
|
|
112
|
-
const refMatch = buttonLine!.match(/@(e\d+)/);
|
|
113
|
-
expect(refMatch).toBeDefined();
|
|
114
|
-
const ref = `@${refMatch![1]}`;
|
|
115
|
-
const result = await handleWriteCommand('click', [ref], bm);
|
|
116
|
-
expect(result).toContain('Clicked');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('fill @ref works after snapshot', async () => {
|
|
120
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
121
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
122
|
-
// Find a textbox ref (Username)
|
|
123
|
-
const textboxLine = snap.split('\n').find(l => l.includes('[textbox]') && l.includes('"Username"'));
|
|
124
|
-
expect(textboxLine).toBeDefined();
|
|
125
|
-
const refMatch = textboxLine!.match(/@(e\d+)/);
|
|
126
|
-
expect(refMatch).toBeDefined();
|
|
127
|
-
const ref = `@${refMatch![1]}`;
|
|
128
|
-
const result = await handleWriteCommand('fill', [ref, 'testuser'], bm);
|
|
129
|
-
expect(result).toContain('Filled');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('hover @ref works after snapshot', async () => {
|
|
133
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
134
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
135
|
-
const linkLine = snap.split('\n').find(l => l.includes('[link]'));
|
|
136
|
-
expect(linkLine).toBeDefined();
|
|
137
|
-
const refMatch = linkLine!.match(/@(e\d+)/);
|
|
138
|
-
const ref = `@${refMatch![1]}`;
|
|
139
|
-
const result = await handleWriteCommand('hover', [ref], bm);
|
|
140
|
-
expect(result).toContain('Hovered');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('html @ref returns innerHTML', async () => {
|
|
144
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
145
|
-
const snap = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
146
|
-
// Find a heading ref
|
|
147
|
-
const headingLine = snap.split('\n').find(l => l.includes('[heading]') && l.includes('"Snapshot Test"'));
|
|
148
|
-
expect(headingLine).toBeDefined();
|
|
149
|
-
const refMatch = headingLine!.match(/@(e\d+)/);
|
|
150
|
-
const ref = `@${refMatch![1]}`;
|
|
151
|
-
const result = await handleReadCommand('html', [ref], bm);
|
|
152
|
-
expect(result).toContain('Snapshot Test');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('css @ref returns computed CSS', async () => {
|
|
156
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
157
|
-
const snap = await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
158
|
-
const headingLine = snap.split('\n').find(l => l.includes('[heading]') && l.includes('"Snapshot Test"'));
|
|
159
|
-
const refMatch = headingLine!.match(/@(e\d+)/);
|
|
160
|
-
const ref = `@${refMatch![1]}`;
|
|
161
|
-
const result = await handleReadCommand('css', [ref, 'font-family'], bm);
|
|
162
|
-
expect(result).toBeTruthy();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('attrs @ref returns element attributes', async () => {
|
|
166
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
167
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
168
|
-
const textboxLine = snap.split('\n').find(l => l.includes('[textbox]') && l.includes('"Username"'));
|
|
169
|
-
const refMatch = textboxLine!.match(/@(e\d+)/);
|
|
170
|
-
const ref = `@${refMatch![1]}`;
|
|
171
|
-
const result = await handleReadCommand('attrs', [ref], bm);
|
|
172
|
-
expect(result).toContain('id');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// ─── Ref Invalidation ───────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
describe('Ref invalidation', () => {
|
|
179
|
-
test('stale ref after goto returns clear error', async () => {
|
|
180
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
181
|
-
await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
182
|
-
// Navigate away — should invalidate refs
|
|
183
|
-
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
|
184
|
-
// Try to use old ref
|
|
185
|
-
try {
|
|
186
|
-
await handleWriteCommand('click', ['@e1'], bm);
|
|
187
|
-
expect(true).toBe(false); // Should not reach here
|
|
188
|
-
} catch (err: any) {
|
|
189
|
-
expect(err.message).toContain('not found');
|
|
190
|
-
expect(err.message).toContain('snapshot');
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('refs cleared on page navigation', async () => {
|
|
195
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
196
|
-
await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
197
|
-
expect(bm.getRefCount()).toBeGreaterThan(0);
|
|
198
|
-
// Navigate
|
|
199
|
-
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
|
200
|
-
expect(bm.getRefCount()).toBe(0);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// ─── Ref Staleness Detection ────────────────────────────────────
|
|
206
|
-
|
|
207
|
-
describe('Ref staleness detection', () => {
|
|
208
|
-
test('ref metadata stores role and name', async () => {
|
|
209
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
210
|
-
await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
211
|
-
// Refs should exist with metadata
|
|
212
|
-
expect(bm.getRefCount()).toBeGreaterThan(0);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test('stale ref after DOM removal gives descriptive error', async () => {
|
|
216
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
217
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
218
|
-
// Find a button ref
|
|
219
|
-
const buttonLine = snap.split('\n').find(l => l.includes('[button]') && l.includes('"Submit"'));
|
|
220
|
-
expect(buttonLine).toBeDefined();
|
|
221
|
-
const refMatch = buttonLine!.match(/@(e\d+)/);
|
|
222
|
-
expect(refMatch).toBeDefined();
|
|
223
|
-
const ref = `@${refMatch![1]}`;
|
|
224
|
-
|
|
225
|
-
// Remove the button from DOM (simulates SPA re-render)
|
|
226
|
-
await handleReadCommand('js', ['document.querySelector("button[type=submit]").remove()'], bm);
|
|
227
|
-
|
|
228
|
-
// Try to click — should get descriptive staleness error
|
|
229
|
-
try {
|
|
230
|
-
await handleWriteCommand('click', [ref], bm);
|
|
231
|
-
expect(true).toBe(false); // Should not reach here
|
|
232
|
-
} catch (err: any) {
|
|
233
|
-
expect(err.message).toContain('stale');
|
|
234
|
-
expect(err.message).toContain('button');
|
|
235
|
-
expect(err.message).toContain('Submit');
|
|
236
|
-
expect(err.message).toContain('snapshot');
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test('valid ref still resolves normally after staleness check', async () => {
|
|
241
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
242
|
-
const snap = await handleMetaCommand('snapshot', ['-i'], bm, shutdown);
|
|
243
|
-
const linkLine = snap.split('\n').find(l => l.includes('[link]'));
|
|
244
|
-
expect(linkLine).toBeDefined();
|
|
245
|
-
const refMatch = linkLine!.match(/@(e\d+)/);
|
|
246
|
-
const ref = `@${refMatch![1]}`;
|
|
247
|
-
// Should work normally — element still exists
|
|
248
|
-
const result = await handleWriteCommand('hover', [ref], bm);
|
|
249
|
-
expect(result).toContain('Hovered');
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// ─── Snapshot Diffing ──────────────────────────────────────────
|
|
254
|
-
|
|
255
|
-
describe('Snapshot diff', () => {
|
|
256
|
-
test('first snapshot -D stores baseline', async () => {
|
|
257
|
-
// Clear any previous snapshot
|
|
258
|
-
bm.setLastSnapshot(null);
|
|
259
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
260
|
-
const result = await handleMetaCommand('snapshot', ['-D'], bm, shutdown);
|
|
261
|
-
expect(result).toContain('no previous snapshot');
|
|
262
|
-
expect(result).toContain('baseline');
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test('snapshot -D shows diff after change', async () => {
|
|
266
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
267
|
-
// Take first snapshot
|
|
268
|
-
await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
269
|
-
// Modify DOM
|
|
270
|
-
await handleReadCommand('js', ['document.querySelector("h1").textContent = "Changed Title"'], bm);
|
|
271
|
-
// Take diff
|
|
272
|
-
const diff = await handleMetaCommand('snapshot', ['-D'], bm, shutdown);
|
|
273
|
-
expect(diff).toContain('---');
|
|
274
|
-
expect(diff).toContain('+++');
|
|
275
|
-
expect(diff).toContain('previous snapshot');
|
|
276
|
-
expect(diff).toContain('current snapshot');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test('snapshot -D with identical page shows no changes', async () => {
|
|
280
|
-
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
|
281
|
-
await handleMetaCommand('snapshot', [], bm, shutdown);
|
|
282
|
-
const diff = await handleMetaCommand('snapshot', ['-D'], bm, shutdown);
|
|
283
|
-
// All lines should be unchanged (prefixed with space)
|
|
284
|
-
const lines = diff.split('\n').filter(l => l.startsWith('+') || l.startsWith('-'));
|
|
285
|
-
// Header lines start with --- and +++ so filter those
|
|
286
|
-
const contentChanges = lines.filter(l => !l.startsWith('---') && !l.startsWith('+++'));
|
|
287
|
-
expect(contentChanges.length).toBe(0);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// ─── Annotated Screenshots ─────────────────────────────────────
|
|
292
|
-
|
|
293
|
-
describe('Annotated screenshots', () => {
|
|
294
|
-
test('snapshot -a creates annotated screenshot', async () => {
|
|
295
|
-
const screenshotPath = '/tmp/browse-test-annotated.png';
|
|
296
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
297
|
-
const result = await handleMetaCommand('snapshot', ['-a', '-o', screenshotPath], bm, shutdown);
|
|
298
|
-
expect(result).toContain('annotated screenshot');
|
|
299
|
-
expect(result).toContain(screenshotPath);
|
|
300
|
-
expect(fs.existsSync(screenshotPath)).toBe(true);
|
|
301
|
-
const stat = fs.statSync(screenshotPath);
|
|
302
|
-
expect(stat.size).toBeGreaterThan(1000);
|
|
303
|
-
fs.unlinkSync(screenshotPath);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
test('snapshot -a uses default path', async () => {
|
|
307
|
-
const defaultPath = '/tmp/browse-annotated.png';
|
|
308
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
309
|
-
const result = await handleMetaCommand('snapshot', ['-a'], bm, shutdown);
|
|
310
|
-
expect(result).toContain('annotated screenshot');
|
|
311
|
-
expect(fs.existsSync(defaultPath)).toBe(true);
|
|
312
|
-
fs.unlinkSync(defaultPath);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test('snapshot -a -i only annotates interactive', async () => {
|
|
316
|
-
const screenshotPath = '/tmp/browse-test-annotated-i.png';
|
|
317
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
318
|
-
const result = await handleMetaCommand('snapshot', ['-i', '-a', '-o', screenshotPath], bm, shutdown);
|
|
319
|
-
expect(result).toContain('[button]');
|
|
320
|
-
expect(result).toContain('[link]');
|
|
321
|
-
expect(result).toContain('annotated screenshot');
|
|
322
|
-
if (fs.existsSync(screenshotPath)) fs.unlinkSync(screenshotPath);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test('annotation overlays are cleaned up', async () => {
|
|
326
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
327
|
-
await handleMetaCommand('snapshot', ['-a'], bm, shutdown);
|
|
328
|
-
// Check that overlays are removed
|
|
329
|
-
const overlays = await handleReadCommand('js', ['document.querySelectorAll(".__browse_annotation__").length'], bm);
|
|
330
|
-
expect(overlays).toBe('0');
|
|
331
|
-
// Clean up default file
|
|
332
|
-
try { fs.unlinkSync('/tmp/browse-annotated.png'); } catch {}
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// ─── Cursor-Interactive ────────────────────────────────────────
|
|
337
|
-
|
|
338
|
-
describe('Cursor-interactive', () => {
|
|
339
|
-
test('snapshot -C finds cursor:pointer elements', async () => {
|
|
340
|
-
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
|
341
|
-
const result = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
|
342
|
-
expect(result).toContain('cursor-interactive');
|
|
343
|
-
expect(result).toContain('@c');
|
|
344
|
-
expect(result).toContain('cursor:pointer');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('snapshot -C includes onclick elements', async () => {
|
|
348
|
-
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
|
349
|
-
const result = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
|
350
|
-
expect(result).toContain('onclick');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test('snapshot -C includes tabindex elements', async () => {
|
|
354
|
-
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
|
355
|
-
const result = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
|
356
|
-
expect(result).toContain('tabindex');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test('@c ref is clickable', async () => {
|
|
360
|
-
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
|
361
|
-
const snap = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
|
362
|
-
// Find a @c ref
|
|
363
|
-
const cLine = snap.split('\n').find(l => l.includes('@c'));
|
|
364
|
-
if (cLine) {
|
|
365
|
-
const refMatch = cLine.match(/@(c\d+)/);
|
|
366
|
-
if (refMatch) {
|
|
367
|
-
const result = await handleWriteCommand('click', [`@${refMatch[1]}`], bm);
|
|
368
|
-
expect(result).toContain('Clicked');
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test('snapshot -C on page with no cursor elements', async () => {
|
|
374
|
-
await handleWriteCommand('goto', [baseUrl + '/empty.html'], bm);
|
|
375
|
-
const result = await handleMetaCommand('snapshot', ['-C'], bm, shutdown);
|
|
376
|
-
// Should not contain cursor-interactive section
|
|
377
|
-
expect(result).not.toContain('cursor-interactive');
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
test('snapshot -i -C combines both modes', async () => {
|
|
381
|
-
await handleWriteCommand('goto', [baseUrl + '/cursor-interactive.html'], bm);
|
|
382
|
-
const result = await handleMetaCommand('snapshot', ['-i', '-C'], bm, shutdown);
|
|
383
|
-
// Should have interactive elements (button, link)
|
|
384
|
-
expect(result).toContain('[button]');
|
|
385
|
-
expect(result).toContain('[link]');
|
|
386
|
-
// And cursor-interactive section
|
|
387
|
-
expect(result).toContain('cursor-interactive');
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// ─── Snapshot Error Paths ───────────────────────────────────────
|
|
392
|
-
|
|
393
|
-
describe('Snapshot errors', () => {
|
|
394
|
-
test('unknown flag throws', async () => {
|
|
395
|
-
try {
|
|
396
|
-
await handleMetaCommand('snapshot', ['--bogus'], bm, shutdown);
|
|
397
|
-
expect(true).toBe(false);
|
|
398
|
-
} catch (err: any) {
|
|
399
|
-
expect(err.message).toContain('Unknown snapshot flag');
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
test('-d without number throws', async () => {
|
|
404
|
-
try {
|
|
405
|
-
await handleMetaCommand('snapshot', ['-d'], bm, shutdown);
|
|
406
|
-
expect(true).toBe(false);
|
|
407
|
-
} catch (err: any) {
|
|
408
|
-
expect(err.message).toContain('Usage');
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test('-s without selector throws', async () => {
|
|
413
|
-
try {
|
|
414
|
-
await handleMetaCommand('snapshot', ['-s'], bm, shutdown);
|
|
415
|
-
expect(true).toBe(false);
|
|
416
|
-
} catch (err: any) {
|
|
417
|
-
expect(err.message).toContain('Usage');
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test('-s with nonexistent selector throws', async () => {
|
|
422
|
-
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
|
423
|
-
try {
|
|
424
|
-
await handleMetaCommand('snapshot', ['-s', '#nonexistent-element-12345'], bm, shutdown);
|
|
425
|
-
expect(true).toBe(false);
|
|
426
|
-
} catch (err: any) {
|
|
427
|
-
expect(err.message).toContain('Selector not found');
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('-o without path throws', async () => {
|
|
432
|
-
try {
|
|
433
|
-
await handleMetaCommand('snapshot', ['-o'], bm, shutdown);
|
|
434
|
-
expect(true).toBe(false);
|
|
435
|
-
} catch (err: any) {
|
|
436
|
-
expect(err.message).toContain('Usage');
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// ─── Combined Flags ─────────────────────────────────────────────
|
|
442
|
-
|
|
443
|
-
describe('Snapshot combined flags', () => {
|
|
444
|
-
test('-i -c -d 2 combines all filters', async () => {
|
|
445
|
-
await handleWriteCommand('goto', [baseUrl + '/snapshot.html'], bm);
|
|
446
|
-
const result = await handleMetaCommand('snapshot', ['-i', '-c', '-d', '2'], bm, shutdown);
|
|
447
|
-
// Should be filtered to interactive, compact, shallow
|
|
448
|
-
expect(result).toContain('[button]');
|
|
449
|
-
expect(result).toContain('[link]');
|
|
450
|
-
// Should NOT contain deep nested non-interactive elements
|
|
451
|
-
expect(result).not.toContain('[heading]');
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
test('closetab last tab auto-creates new', async () => {
|
|
455
|
-
// Get down to 1 tab
|
|
456
|
-
const tabs = await bm.getTabListWithTitles();
|
|
457
|
-
for (let i = 1; i < tabs.length; i++) {
|
|
458
|
-
await bm.closeTab(tabs[i].id);
|
|
459
|
-
}
|
|
460
|
-
expect(bm.getTabCount()).toBe(1);
|
|
461
|
-
// Close the last tab
|
|
462
|
-
const lastTab = (await bm.getTabListWithTitles())[0];
|
|
463
|
-
await bm.closeTab(lastTab.id);
|
|
464
|
-
// Should have auto-created a new tab
|
|
465
|
-
expect(bm.getTabCount()).toBe(1);
|
|
466
|
-
});
|
|
467
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State file TTL security tests
|
|
3
|
-
*
|
|
4
|
-
* Verifies that state save includes savedAt timestamp and state load
|
|
5
|
-
* warns on old state files.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect } from 'bun:test';
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
|
|
12
|
-
const META_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/meta-commands.ts'), 'utf-8');
|
|
13
|
-
|
|
14
|
-
describe('State file TTL', () => {
|
|
15
|
-
test('state save includes savedAt timestamp in output', () => {
|
|
16
|
-
// Verify the save code writes savedAt to the state file
|
|
17
|
-
const saveBlock = META_SRC.slice(
|
|
18
|
-
META_SRC.indexOf("if (action === 'save')"),
|
|
19
|
-
META_SRC.indexOf("if (action === 'load')"),
|
|
20
|
-
);
|
|
21
|
-
expect(saveBlock).toContain('savedAt: new Date().toISOString()');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('state load warns when savedAt is older than 7 days', () => {
|
|
25
|
-
// Verify the load code checks savedAt age and warns
|
|
26
|
-
const loadStart = META_SRC.indexOf("if (action === 'load')");
|
|
27
|
-
// Find the second occurrence of "Usage: state save|load" (appears after the load block)
|
|
28
|
-
const loadEnd = META_SRC.indexOf("Usage: state save|load", loadStart);
|
|
29
|
-
const loadBlock = META_SRC.slice(loadStart, loadEnd);
|
|
30
|
-
expect(loadBlock).toContain('data.savedAt');
|
|
31
|
-
expect(loadBlock).toContain('SEVEN_DAYS');
|
|
32
|
-
expect(loadBlock).toContain('console.warn');
|
|
33
|
-
expect(loadBlock).toContain('days old');
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tiny Bun.serve for test fixtures
|
|
3
|
-
* Serves HTML files from test/fixtures/ on a random available port
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
|
|
9
|
-
const FIXTURES_DIR = path.resolve(import.meta.dir, 'fixtures');
|
|
10
|
-
|
|
11
|
-
export function startTestServer(port: number = 0): { server: ReturnType<typeof Bun.serve>; url: string } {
|
|
12
|
-
const server = Bun.serve({
|
|
13
|
-
port,
|
|
14
|
-
hostname: '127.0.0.1',
|
|
15
|
-
fetch(req) {
|
|
16
|
-
const url = new URL(req.url);
|
|
17
|
-
|
|
18
|
-
// Echo endpoint — returns request headers as JSON
|
|
19
|
-
if (url.pathname === '/echo') {
|
|
20
|
-
const headers: Record<string, string> = {};
|
|
21
|
-
req.headers.forEach((value, key) => { headers[key] = value; });
|
|
22
|
-
return new Response(JSON.stringify(headers, null, 2), {
|
|
23
|
-
headers: { 'Content-Type': 'application/json' },
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let filePath = url.pathname === '/' ? '/basic.html' : url.pathname;
|
|
28
|
-
|
|
29
|
-
// Remove leading slash
|
|
30
|
-
filePath = filePath.replace(/^\//, '');
|
|
31
|
-
const fullPath = path.join(FIXTURES_DIR, filePath);
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(fullPath)) {
|
|
34
|
-
return new Response('Not Found', { status: 404 });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
38
|
-
const ext = path.extname(fullPath);
|
|
39
|
-
const contentType = ext === '.html' ? 'text/html' : 'text/plain';
|
|
40
|
-
|
|
41
|
-
return new Response(content, {
|
|
42
|
-
headers: { 'Content-Type': contentType },
|
|
43
|
-
});
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const url = `http://127.0.0.1:${server.port}`;
|
|
48
|
-
return { server, url };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// If run directly, start and print URL
|
|
52
|
-
if (import.meta.main) {
|
|
53
|
-
const { server, url } = startTestServer(9450);
|
|
54
|
-
console.log(`Test server running at ${url}`);
|
|
55
|
-
console.log(`Fixtures: ${FIXTURES_DIR}`);
|
|
56
|
-
console.log('Press Ctrl+C to stop');
|
|
57
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'bun:test';
|
|
2
|
-
import { validateNavigationUrl } from '../src/url-validation';
|
|
3
|
-
|
|
4
|
-
describe('validateNavigationUrl', () => {
|
|
5
|
-
it('allows http URLs', async () => {
|
|
6
|
-
await expect(validateNavigationUrl('http://example.com')).resolves.toBeUndefined();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('allows https URLs', async () => {
|
|
10
|
-
await expect(validateNavigationUrl('https://example.com/path?q=1')).resolves.toBeUndefined();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('allows localhost', async () => {
|
|
14
|
-
await expect(validateNavigationUrl('http://localhost:3000')).resolves.toBeUndefined();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('allows 127.0.0.1', async () => {
|
|
18
|
-
await expect(validateNavigationUrl('http://127.0.0.1:8080')).resolves.toBeUndefined();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('allows private IPs', async () => {
|
|
22
|
-
await expect(validateNavigationUrl('http://192.168.1.1')).resolves.toBeUndefined();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('blocks file:// scheme', async () => {
|
|
26
|
-
await expect(validateNavigationUrl('file:///etc/passwd')).rejects.toThrow(/scheme.*not allowed/i);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('blocks javascript: scheme', async () => {
|
|
30
|
-
await expect(validateNavigationUrl('javascript:alert(1)')).rejects.toThrow(/scheme.*not allowed/i);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('blocks data: scheme', async () => {
|
|
34
|
-
await expect(validateNavigationUrl('data:text/html,<h1>hi</h1>')).rejects.toThrow(/scheme.*not allowed/i);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('blocks AWS/GCP metadata endpoint', async () => {
|
|
38
|
-
await expect(validateNavigationUrl('http://169.254.169.254/latest/meta-data/')).rejects.toThrow(/cloud metadata/i);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('blocks GCP metadata hostname', async () => {
|
|
42
|
-
await expect(validateNavigationUrl('http://metadata.google.internal/computeMetadata/v1/')).rejects.toThrow(/cloud metadata/i);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('blocks Azure metadata hostname', async () => {
|
|
46
|
-
await expect(validateNavigationUrl('http://metadata.azure.internal/metadata/instance')).rejects.toThrow(/cloud metadata/i);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('blocks metadata hostname with trailing dot', async () => {
|
|
50
|
-
await expect(validateNavigationUrl('http://metadata.google.internal./computeMetadata/v1/')).rejects.toThrow(/cloud metadata/i);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('blocks metadata IP in hex form', async () => {
|
|
54
|
-
await expect(validateNavigationUrl('http://0xA9FEA9FE/')).rejects.toThrow(/cloud metadata/i);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('blocks metadata IP in decimal form', async () => {
|
|
58
|
-
await expect(validateNavigationUrl('http://2852039166/')).rejects.toThrow(/cloud metadata/i);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('blocks metadata IP in octal form', async () => {
|
|
62
|
-
await expect(validateNavigationUrl('http://0251.0376.0251.0376/')).rejects.toThrow(/cloud metadata/i);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('blocks IPv6 metadata with brackets', async () => {
|
|
66
|
-
await expect(validateNavigationUrl('http://[fd00::]/')).rejects.toThrow(/cloud metadata/i);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('throws on malformed URLs', async () => {
|
|
70
|
-
await expect(validateNavigationUrl('not-a-url')).rejects.toThrow(/Invalid URL/i);
|
|
71
|
-
});
|
|
72
|
-
});
|