-
Notifications
You must be signed in to change notification settings - Fork 154
Expand file tree
/
Copy pathvalidate-serverlogic.test.js
More file actions
269 lines (234 loc) · 9.25 KB
/
Copy pathvalidate-serverlogic.test.js
File metadata and controls
269 lines (234 loc) · 9.25 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { createTempProject, writeProjectFile } = require('./test-utils');
const VALIDATOR_PATH = path.join(
__dirname,
'..',
'..',
'skills',
'add-server-logic',
'scripts',
'validate-serverlogic.js'
);
function runValidator(projectRoot) {
const input = JSON.stringify({ cwd: projectRoot });
return spawnSync(process.execPath, [VALIDATOR_PATH], {
input,
encoding: 'utf8',
});
}
function setupProject(projectRoot) {
writeProjectFile(projectRoot, 'powerpages.config.json', '{}');
}
function writeServerLogic(projectRoot, name, jsContent, ymlContent) {
const dir = `.powerpages-site/server-logic/${name}`;
writeProjectFile(projectRoot, `${dir}/${name}.js`, jsContent);
if (ymlContent) {
writeProjectFile(projectRoot, `${dir}/${name}.serverlogic.yml`, ymlContent);
}
}
const VALID_YML = `adx_serverlogic_adx_webrole:
- 11111111-1111-1111-1111-111111111111
description: Test endpoint
display_name: Test
id: 22222222-2222-2222-2222-222222222222
name: test-endpoint`;
const VALID_JS = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
return JSON.stringify({ status: "success" });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
test('valid server logic passes validation', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
writeServerLogic(projectRoot, 'test-endpoint', VALID_JS, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 0, result.stderr);
});
test('async function without await is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const asyncNoAwaitJs = `async function get() {
try {
Server.Logger.Log("test-endpoint GET called");
var records = Server.Connector.Dataverse.RetrieveMultipleRecords("contacts", "?$top=10");
return JSON.stringify({ status: "success", data: records });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', asyncNoAwaitJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /function 'get' is marked async but contains no await/);
});
test('async function with await passes validation', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const asyncWithAwaitJs = `async function post() {
try {
Server.Logger.Log("test-endpoint POST called");
var response = await Server.Connector.HttpClient.PostAsync("https://api.example.com/data", JSON.stringify({ key: "value" }));
return JSON.stringify({ status: "success", data: response });
} catch (err) {
Server.Logger.Error("test-endpoint POST failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', asyncWithAwaitJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 0, result.stderr);
});
test('missing js file is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const dir = path.join(projectRoot, '.powerpages-site', 'server-logic', 'test-endpoint');
fs.mkdirSync(dir, { recursive: true });
writeProjectFile(projectRoot, '.powerpages-site/server-logic/test-endpoint/test-endpoint.serverlogic.yml', VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /missing \.js file/);
});
test('missing yml file is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
writeServerLogic(projectRoot, 'test-endpoint', VALID_JS, null);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /missing metadata file/);
});
test('missing try/catch is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const noTryCatchJs = `function get() {
Server.Logger.Log("test-endpoint GET called");
return JSON.stringify({ status: "success" });
}`;
writeServerLogic(projectRoot, 'test-endpoint', noTryCatchJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /missing try\/catch/);
});
test('disallowed function name is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const badFnJs = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
return JSON.stringify({ status: "success" });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}
function helper() {
return "not allowed";
}`;
writeServerLogic(projectRoot, 'test-endpoint', badFnJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /found additional top-level functions: helper/);
});
test('nested helper function inside handler is not flagged as disallowed', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const nestedHelperJs = `function get() {
try {
function buildResponse(data) { return JSON.stringify({ status: "success", data: data }); }
Server.Logger.Log("test-endpoint GET called");
return buildResponse("hello");
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', nestedHelperJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 0, result.stderr);
});
test('module.exports usage is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const exportsJs = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
return JSON.stringify({ status: "success" });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}
module.exports.get = get;`;
writeServerLogic(projectRoot, 'test-endpoint', exportsJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /module\.exports\/exports assignments are not allowed/);
});
test('try without catch is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const tryFinallyJs = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
return JSON.stringify({ status: "success" });
} finally {
Server.Logger.Log("cleanup");
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', tryFinallyJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /missing a catch block/);
});
test('yml name mismatch is flagged', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const mismatchYml = VALID_YML.replace('name: test-endpoint', 'name: wrong-name');
writeServerLogic(projectRoot, 'test-endpoint', VALID_JS, mismatchYml);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /does not match folder name/);
});
test('startswith( inside string literal is flagged (with-pattern)', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const startsWithJs = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
var query = "$filter=startswith(name,'INV-')";
return JSON.stringify({ status: "success", query: query });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', startsWithJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 2);
assert.match(result.stderr, /contains the substring 'with\('/);
});
test('split startswith( workaround passes validation', (t) => {
const projectRoot = createTempProject(t);
setupProject(projectRoot);
const splitJs = `function get() {
try {
Server.Logger.Log("test-endpoint GET called");
var query = "$filter=startswith" + "(name,'INV-')";
return JSON.stringify({ status: "success", query: query });
} catch (err) {
Server.Logger.Error("test-endpoint GET failed: " + err.message);
return JSON.stringify({ status: "error", message: err.message });
}
}`;
writeServerLogic(projectRoot, 'test-endpoint', splitJs, VALID_YML);
const result = runValidator(projectRoot);
assert.equal(result.status, 0, result.stderr);
});