-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathtest-web-ui-assets.js
More file actions
133 lines (121 loc) · 6.08 KB
/
Copy pathtest-web-ui-assets.js
File metadata and controls
133 lines (121 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
const http = require('http');
const { assert } = require('./helpers');
function getText(port, requestPath, timeoutMs = 2000) {
return new Promise((resolve, reject) => {
const req = http.request({
hostname: '127.0.0.1',
port,
path: requestPath,
method: 'GET'
}, (res) => {
let body = '';
res.setEncoding('utf-8');
res.on('data', chunk => body += chunk);
res.on('end', () => {
resolve({
statusCode: res.statusCode,
headers: res.headers || {},
body
});
});
});
req.on('error', reject);
req.setTimeout(timeoutMs, () => {
req.destroy(new Error('Request timeout'));
});
req.end();
});
}
module.exports = async function testWebUiAssets(ctx) {
const { port } = ctx;
const rootPage = await getText(port, '/');
assert(rootPage.statusCode === 200, 'root web ui page should return 200');
assert(
/^text\/html\b/.test(String(rootPage.headers['content-type'] || '')),
'root web ui page should return html content type'
);
assert(rootPage.body.includes('id="panel-market"'), 'root web ui page should inline market panel');
assert(rootPage.body.includes('class="modal modal-wide skills-modal"'), 'root web ui page should inline skills modal');
assert(rootPage.body.includes('src="/web-ui/app.js"'), 'root web ui page should point to the absolute app entry');
assert(!rootPage.body.includes('src="web-ui/app.js"'), 'root web ui page should not use a relative app entry');
assert(!/<!--\s*@include\s+/.test(rootPage.body), 'root web ui page should not leak include directives');
const bundledIndex = await getText(port, '/web-ui/index.html');
assert(bundledIndex.statusCode === 200, '/web-ui/index.html should return 200');
assert(
/^text\/html\b/.test(String(bundledIndex.headers['content-type'] || '')),
'/web-ui/index.html should return html content type'
);
assert(bundledIndex.body.includes('id="settings-panel-data"'), '/web-ui/index.html should inline settings partials');
assert(bundledIndex.body.includes('src="/web-ui/app.js"'), '/web-ui/index.html should point to the absolute app entry');
assert(
bundledIndex.body.includes('src="/res/vue.global.prod.js"'),
'/web-ui/index.html should use the production Vue browser build'
);
assert(
!bundledIndex.body.includes('src="/res/runtime.global.prod.js"'),
'/web-ui/index.html should not use the runtime-only Vue build'
);
assert(!bundledIndex.body.includes('src="web-ui/app.js"'), '/web-ui/index.html should not use a relative app entry');
assert(!/<!--\s*@include\s+/.test(bundledIndex.body), '/web-ui/index.html should not leak include directives');
const bundledIndexWithSlash = await getText(port, '/web-ui/');
assert(bundledIndexWithSlash.statusCode === 404, '/web-ui/ should preserve the legacy 404 contract');
assert(
/^text\/plain\b/.test(String(bundledIndexWithSlash.headers['content-type'] || '')),
'/web-ui/ should preserve plain-text not found semantics'
);
const appEntry = await getText(port, '/web-ui/app.js');
assert(appEntry.statusCode === 200, 'app entry should return 200');
assert(
/^application\/javascript\b/.test(String(appEntry.headers['content-type'] || '')),
'app entry should return javascript content type'
);
assert(appEntry.body.includes('document.addEventListener(\'DOMContentLoaded\''), 'app entry should contain the executable bootstrap');
assert(
!/(?:^|\n)\s*import\s+(?:[\s\S]*?\s+from\s+)?['"]\.[^'"]+['"]\s*;?/.test(appEntry.body),
'app entry should not leak split relative module imports'
);
assert(
!/(?:^|\n)\s*export\s+\*\s+from\s+['"]\.[^'"]+['"]\s*;?/.test(appEntry.body),
'app entry should not leak split re-export directives'
);
assert(appEntry.body.includes('onClaudeModelChange'), 'app entry should expose Claude model handler');
const logicEntry = await getText(port, '/web-ui/logic.mjs');
assert(logicEntry.statusCode === 200, 'logic entry should return 200');
assert(
/^application\/javascript\b/.test(String(logicEntry.headers['content-type'] || '')),
'logic entry should return javascript content type'
);
assert(
logicEntry.body.includes('export function normalizeClaudeValue'),
'logic entry should preserve named exports for compatibility'
);
assert(
!/(?:^|\n)\s*export\s+\*\s+from\s+['"]\.[^'"]+['"]\s*;?/.test(logicEntry.body),
'logic entry should not leak split re-export directives'
);
const cssAsset = await getText(port, '/web-ui/styles/base-theme.css');
assert(cssAsset.statusCode === 404, 'new split css implementation detail should stay private');
assert(
/^text\/plain\b/.test(String(cssAsset.headers['content-type'] || '')),
'private split css asset should return plain-text not found'
);
const bundledCss = await getText(port, '/web-ui/styles.css');
assert(bundledCss.statusCode === 200, 'styles entry should return 200');
assert(
/^text\/css\b/.test(String(bundledCss.headers['content-type'] || '')),
'styles entry should return css content type'
);
assert(bundledCss.body.includes('--radius-md:'), 'styles entry should include theme variables');
assert(
!/@import\s+url\(['"]\.\/styles\//.test(bundledCss.body),
'styles entry should not leak split css imports'
);
const moduleAsset = await getText(port, '/web-ui/modules/app.constants.mjs');
assert(moduleAsset.statusCode === 404, 'new split module implementation detail should stay private');
assert(
/^text\/plain\b/.test(String(moduleAsset.headers['content-type'] || '')),
'private split module asset should return plain-text not found'
);
const traversal = await getText(port, '/web-ui/../cli.js');
assert(traversal.statusCode === 403, 'path traversal outside web-ui should be forbidden');
};