Skip to content

Commit 9d5d289

Browse files
committed
improvements
1 parent 34f6ce8 commit 9d5d289

File tree

6 files changed

+146
-137
lines changed

6 files changed

+146
-137
lines changed

scim.test.js

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import path from 'path';
22
import axios from 'axios';
3-
import { getAxiosInstance, getConfig } from './src/helpers.js';
4-
5-
const config = getConfig();
3+
import { getAxiosInstance } from './src/helpers.js';
64

75
import runBasicTests from './src/basics.js';
8-
runBasicTests(config);
96
import runUserTests from './src/users.js';
107
import runGroupTests from './src/groups.js';
118
import runResourceTypeTests from './src/resourcetypes.js';
129
import { runTests as runSchemaTests } from './src/schemas.js';
1310
import fs from 'fs/promises';
1411

1512
// Function to process schemas and resource types
16-
function processResourcesAndSchemas(resourceTypes, schemas) {
13+
function processResourcesAndSchemas(config, resourceTypes, schemas) {
1714
// get schema and schema extensions for user
1815
const userResourceType = resourceTypes.find(e => e.name === 'Users');
1916
const userSchemaId = userResourceType.schema;
@@ -38,38 +35,53 @@ function processResourcesAndSchemas(resourceTypes, schemas) {
3835
}
3936
}
4037

41-
// Initialize what we have and what we need to fetch
42-
const axiosInstance = getAxiosInstance();
43-
let resourceTypesPromise, schemasPromise;
38+
// Wait for both promises to resolve
39+
// Define the main function that runs all tests
40+
export async function runAllTests(config) {
41+
// Initialize what we have and what we need to fetch
42+
const axiosInstance = getAxiosInstance(config);
43+
let resourceTypesPromise, schemasPromise;
4444

45-
if(config?.detectResourceTypes) {
46-
runResourceTypeTests(config);
47-
resourceTypesPromise = axiosInstance.get('/ResourceTypes')
48-
.then(response => response.data.Resources);
49-
} else {
50-
// Import resource types from file since detectResourceTypes is false
51-
resourceTypesPromise = fs.readFile(path.resolve('./src/resourceTypes.json'), 'utf8')
52-
.then(data => JSON.parse(data).Resources)
53-
.then(resourceTypes => Promise.resolve(resourceTypes));
54-
}
45+
if (config?.detectResourceTypes) {
46+
runResourceTypeTests(config);
47+
resourceTypesPromise = axiosInstance.get('/ResourceTypes')
48+
.then(response => response.data.Resources);
49+
} else {
50+
// Import resource types from file since detectResourceTypes is false
51+
resourceTypesPromise = fs.readFile(path.resolve('./src/resourceTypes.json'), 'utf8')
52+
.then(data => JSON.parse(data).Resources)
53+
.then(resourceTypes => Promise.resolve(resourceTypes));
54+
}
5555

5656

57-
if (config?.detectSchema) {
58-
runSchemaTests(config);
59-
schemasPromise = axiosInstance.get('/Schemas')
60-
.then(response => response.data.Resources);
61-
} else {
62-
// Import schemas from file since detectSchema is false
63-
schemasPromise = fs.readFile(path.resolve('./src/schemas.json'), 'utf8')
64-
.then(data => JSON.parse(data).Resources)
65-
.then(schemas => Promise.resolve(schemas));
66-
}
57+
if (config?.detectSchema) {
58+
runSchemaTests(config);
59+
schemasPromise = axiosInstance.get('/Schemas')
60+
.then(response => response.data.Resources);
61+
} else {
62+
// Import schemas from file since detectSchema is false
63+
schemasPromise = fs.readFile(path.resolve('./src/schemas.json'), 'utf8')
64+
.then(data => JSON.parse(data).Resources)
65+
.then(schemas => Promise.resolve(schemas));
66+
}
6767

68-
// Wait for both promises to resolve
69-
Promise.all([resourceTypesPromise, schemasPromise])
70-
.then(([resourceTypes, schemas]) => {
71-
processResourcesAndSchemas(resourceTypes, schemas);
72-
})
73-
.catch(error => {
68+
try {
69+
runBasicTests(config);
70+
const [resourceTypes, schemas] = await Promise.all([resourceTypesPromise, schemasPromise]);
71+
processResourcesAndSchemas(config, resourceTypes, schemas);
72+
console.log('done and return');
73+
return { success: true };
74+
} catch (error) {
7475
console.error('Error fetching SCIM resources:', error);
75-
});
76+
return { success: false, error };
77+
}
78+
}
79+
80+
// Run the tests by default when the file is imported
81+
runAllTests(
82+
{
83+
baseURL: process.env.BASE_URL,
84+
authHeader: process.env.AUTH_HEADER,
85+
...JSON.parse(process.env.CONFIG),
86+
}
87+
);

server.js

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
66
import axios from 'axios';
77
import { run } from 'node:test';
88
import { ndjson } from './reporters/ndjson-reporter.js';
9+
910
import { Writable } from 'stream';
1011

1112
const __filename = fileURLToPath(import.meta.url);
@@ -35,9 +36,63 @@ async function verifyTurnstileToken(token, remoteip) {
3536
}
3637
}
3738

39+
// Queue implementation for processing requests
40+
const requestQueue = [];
41+
let isProcessing = false;
42+
43+
async function processQueue() {
44+
if (isProcessing || requestQueue.length === 0) return;
45+
46+
isProcessing = true;
47+
const { req, res, resolve, reject } = requestQueue.shift();
48+
49+
try {
50+
res.setHeader('Content-Type', 'application/x-ndjson');
51+
res.setHeader('Transfer-Encoding', 'chunked');
52+
53+
process.env.BASE_URL = req.body.url;
54+
process.env.AUTH_HEADER = req.body.authHeader;
55+
process.env.CONFIG = JSON.stringify(req.body);
56+
57+
const responseStream = new Writable({
58+
write(chunk, encoding, callback) {
59+
res.write(chunk);
60+
callback();
61+
}
62+
});
63+
64+
const testRun = run({
65+
files: [path.resolve(__dirname, 'scim.test.js')],
66+
concurrency: 1,
67+
});
68+
69+
testRun.on('test:fail', () => {
70+
process.exitCode = 1;
71+
});
72+
73+
testRun.on('end', () => {
74+
res.end();
75+
process.env.BASE_URL = '';
76+
process.env.AUTH_HEADER = '';
77+
process.env.CONFIG = '';
78+
isProcessing = false; // Only set to false after test run completes
79+
processQueue(); // Process next request if any
80+
resolve();
81+
});
82+
83+
testRun.pipe(ndjson()).pipe(responseStream);
84+
} catch (error) {
85+
if (!res.headersSent) {
86+
res.status(500).json({ success: false, error: error.message });
87+
}
88+
isProcessing = false; // Set to false if there's an error
89+
processQueue(); // Try next request
90+
reject(error);
91+
}
92+
}
93+
3894
// HTTP endpoint for running tests with streaming response
3995
app.post('/run-tests', async (req, res) => {
40-
4196
if (process.env.TURNSTILE_ENABLED === 'true') {
4297
const token = req.headers['cf-challenge-token'];
4398
const remoteip = req.headers['cf-connecting-ip'];
@@ -49,34 +104,20 @@ app.post('/run-tests', async (req, res) => {
49104
}
50105
}
51106

52-
// Set environment variables from the request
53-
process.env.BASE_URL = req.body.url;
54-
process.env.AUTH_HEADER = req.body.authHeader;
55-
process.env.CONFIG = JSON.stringify(req.body);
56-
57-
// Create a writable stream that forwards to the HTTP response
58-
const responseStream = new Writable({
59-
write(chunk, encoding, callback) {
60-
res.write(chunk);
61-
callback();
62-
}
63-
});
64-
65-
// Run tests directly and pipe output to response
66-
const testRun = run({
67-
files: [path.resolve(__dirname, 'scim.test.js')],
68-
concurrency: 1,
107+
// Add request to queue
108+
const promise = new Promise((resolve, reject) => {
109+
requestQueue.push({ req, res, resolve, reject });
69110
});
70111

71-
testRun.on('test:fail', () => {
72-
process.exitCode = 1;
73-
});
74-
75-
testRun.on('end', () => {
76-
res.end();
77-
});
78-
79-
testRun.pipe(ndjson()).pipe(responseStream);
112+
// Trigger queue processing
113+
processQueue();
114+
115+
// Wait for this request to complete
116+
try {
117+
await promise;
118+
} catch (error) {
119+
// Error already handled in processQueue
120+
}
80121
});
81122

82123
// For traditional server

src/basics.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import test from 'node:test';
22
import assert from 'node:assert';
3-
import { getConfig, getAxiosInstance } from './helpers.js';
3+
import { getAxiosInstance } from './helpers.js';
44

5-
function runTests() {
6-
const config = getConfig();
5+
function runTests(config) {
76

87
test.describe('Basic tests', function () {
98
test('Base URL should not contain any query parameters', function () {

src/groups.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import test from 'node:test';
22
import assert from 'node:assert';
3-
import { getAxiosInstance, getConfig } from './helpers.js';
3+
import { getAxiosInstance } from './helpers.js';
44
import dotenv from 'dotenv';
55
import Ajv from 'ajv';
66

@@ -59,7 +59,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
5959
});
6060

6161
test('Retrieves a list of groups', async (t) => {
62-
const testAxios = getAxiosInstance(getConfig(), t);
62+
const testAxios = getAxiosInstance(configuration, t);
6363
const response = await testAxios.get('/Groups');
6464
assert.strictEqual(response.status, 200, 'GET /Groups should return status code 200');
6565
assert.strictEqual(response.data.schemas[0], 'urn:ietf:params:scim:api:messages:2.0:ListResponse', 'Response should contain the correct schema');
@@ -77,7 +77,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
7777
});
7878

7979
test('Retrieves a single group', async (t) => {
80-
const testAxios = getAxiosInstance(getConfig(), t);
80+
const testAxios = getAxiosInstance(configuration, t);
8181
if (!sharedState.groups || sharedState.groups.length === 0) {
8282
t.skip('Previous test failed or no groups found in shared state');
8383
return;
@@ -96,14 +96,14 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
9696
});
9797

9898
test('Handles retrieval of a non-existing group', async (t) => {
99-
const testAxios = getAxiosInstance(getConfig(), t);
99+
const testAxios = getAxiosInstance(configuration, t);
100100
const response = await testAxios.get('/Groups/9876543210123456');
101101
assert.strictEqual(response.status, 404, 'A non-existing group should return 404');
102102
assert.strictEqual(response.data.schemas[0], 'urn:ietf:params:scim:api:messages:2.0:Error', 'Error response should contain the correct error schema');
103103
});
104104

105105
test('Paginates groups using startIndex', async (t) => {
106-
const testAxios = getAxiosInstance(getConfig(), t);
106+
const testAxios = getAxiosInstance(configuration, t);
107107
const startIndex = 20;
108108
const count = 5;
109109
const response = await testAxios.get(`/Groups?startIndex=${startIndex}&count=${count}`);
@@ -114,7 +114,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
114114
});
115115

116116
test('Sorts groups by displayName', async (t) => {
117-
const testAxios = getAxiosInstance(getConfig(), t);
117+
const testAxios = getAxiosInstance(configuration, t);
118118
const response = await testAxios.get('/Groups?sortBy=displayName');
119119
assert.strictEqual(response.status, 200, 'Sort request should return 200 OK');
120120
assert.strictEqual(response.data.schemas[0], 'urn:ietf:params:scim:api:messages:2.0:ListResponse', 'Response should use the correct SCIM list response schema');
@@ -127,7 +127,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
127127
if (configuration?.groups?.operations?.includes('POST')) {
128128
for (const [index, creation] of (configuration.groups.post_tests || []).entries()) {
129129
test(`Creates a new group - Alternative ${index + 1}`, async (t) => {
130-
const testAxios = getAxiosInstance(getConfig(), t);
130+
const testAxios = getAxiosInstance(configuration, t);
131131

132132
creation.request = await populateUserIds(creation.request, configuration, t);
133133

@@ -147,7 +147,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
147147
}
148148

149149
test('Returns errors when creating an invalid group', async (t) => {
150-
const testAxios = getAxiosInstance(getConfig(), t);
150+
const testAxios = getAxiosInstance(configuration, t);
151151
// displayName is always required
152152
const newGroup = {
153153
schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
@@ -164,7 +164,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
164164
if (configuration?.groups?.operations?.includes('PUT')) {
165165
for (const [index, update] of (configuration.groups.put_tests || []).entries()) {
166166
test(`Updates a group using PUT - Alternative ${index + 1}`, async (t) => {
167-
const testAxios = getAxiosInstance(getConfig(), t);
167+
const testAxios = getAxiosInstance(configuration, t);
168168

169169
const replaceId = !update.id || update.id === 'AUTO' ? sharedState.groups?.[0]?.id : update.id;
170170

@@ -191,7 +191,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
191191
if (configuration?.groups?.operations?.includes('PATCH')) {
192192
for (const [index, patch] of (configuration.groups.patch_tests || []).entries()) {
193193
test(`Updates a group using PATCH - Alternative ${index + 1}`, async (t) => {
194-
const testAxios = getAxiosInstance(getConfig(), t);
194+
const testAxios = getAxiosInstance(configuration, t);
195195

196196
const replaceId = !patch.id || patch.id === 'AUTO' ? sharedState.groups?.[0]?.id : patch.id;
197197

@@ -215,7 +215,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
215215
}
216216

217217
test('Assigns a user to a group', async (t) => {
218-
const testAxios = getAxiosInstance(getConfig(), t);
218+
const testAxios = getAxiosInstance(configuration, t);
219219
// Retrieve a user
220220
const userResponse = await testAxios.get('/Users');
221221
assert.strictEqual(userResponse.status, 200, 'GET /Users should return status code 200');
@@ -251,7 +251,7 @@ function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
251251
if (configuration?.groups?.operations?.includes('DELETE')) {
252252
for (const [index, deletion] of (configuration.groups.delete_tests || []).entries()) {
253253
test(`Deletes a group - Alternative ${index + 1}`, async (t) => {
254-
const testAxios = getAxiosInstance(getConfig(), t);
254+
const testAxios = getAxiosInstance(configuration, t);
255255

256256
const deleteId = !deletion.id || deletion.id === 'AUTO' ? sharedState.createdGroup?.id : deletion.id;
257257

0 commit comments

Comments
 (0)