Skip to content

Commit 3b5bc44

Browse files
committed
updates
1 parent f512fef commit 3b5bc44

File tree

10 files changed

+204
-79
lines changed

10 files changed

+204
-79
lines changed

Dockerfile.cli

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# docker build -f Dockerfile.cli -t my-cli .
2+
# docker run -e CONFIG_FILE=/app/config.yaml -e AUTH_HEADER="Bearer CdVrlXiAtgubUkpkS14KUdSN6K4Kx8tkppjtvaWOHVJIzbo3heh0w7OY30DD" -e BASE_URL="https://api.scim.dev/scim/v2" -v $(pwd)/site/.vitepress/theme/components/config.yaml:/app/config.yaml my-cli
3+
4+
FROM node:20-alpine
5+
6+
WORKDIR /app
7+
8+
# Copy package.json and package-lock.json
9+
COPY package*.json ./
10+
11+
# Install dependencies
12+
RUN npm ci --only=production
13+
14+
# Copy the rest of the application
15+
COPY . .
16+
17+
# Set the entrypoint
18+
ENTRYPOINT ["node", "--test", "--test-reporter=./test/reporters/clean-spec.js"]

scim.test.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'path';
22
import axios from 'axios';
3-
import { getAxiosInstance } from './src/helpers.js';
3+
import { getAxiosInstance, writeHarFile } from './src/helpers.js';
44

55
import runBasicTests from './src/basics.js';
66
import runUserTests from './src/users.js';
@@ -11,7 +11,7 @@ import fs from 'fs/promises';
1111
import { parse } from 'yaml'
1212

1313
// Function to process schemas and resource types
14-
function processResourcesAndSchemas(config, resourceTypes, schemas) {
14+
async function processResourcesAndSchemas(config, resourceTypes, schemas) {
1515
// get schema and schema extensions for user
1616
const userResourceType = resourceTypes.find(e => e.name === 'Users');
1717
const userSchemaId = userResourceType.schema;
@@ -28,23 +28,22 @@ function processResourcesAndSchemas(config, resourceTypes, schemas) {
2828
const groupSchemaExtensions = schemas.filter(e => groupSchemaExtensionsIds && groupSchemaExtensionsIds.includes(e.id));
2929

3030
if (config?.users?.enabled) {
31-
runUserTests(userSchema, userSchemaExtensions, config);
31+
await runUserTests(userSchema, userSchemaExtensions, config);
3232
}
3333

3434
if (config?.groups?.enabled) {
35-
runGroupTests(groupSchema, groupSchemaExtensions, config);
35+
await runGroupTests(groupSchema, groupSchemaExtensions, config);
3636
}
3737
}
3838

39-
// Wait for both promises to resolve
4039
// Define the main function that runs all tests
4140
export async function runAllTests(config) {
4241
// Initialize what we have and what we need to fetch
4342
const axiosInstance = getAxiosInstance(config);
4443
let resourceTypesPromise, schemasPromise;
4544

4645
if (config?.detectResourceTypes) {
47-
runResourceTypeTests(config);
46+
await runResourceTypeTests(config);
4847
resourceTypesPromise = axiosInstance.get('/ResourceTypes')
4948
.then(response => response.data.Resources);
5049
} else {
@@ -56,7 +55,7 @@ export async function runAllTests(config) {
5655

5756

5857
if (config?.detectSchema) {
59-
runSchemaTests(config);
58+
await runSchemaTests(config);
6059
schemasPromise = axiosInstance.get('/Schemas')
6160
.then(response => response.data.Resources);
6261
} else {
@@ -67,10 +66,9 @@ export async function runAllTests(config) {
6766
}
6867

6968
try {
70-
runBasicTests(config);
69+
await runBasicTests(config);
7170
const [resourceTypes, schemas] = await Promise.all([resourceTypesPromise, schemasPromise]);
72-
processResourcesAndSchemas(config, resourceTypes, schemas);
73-
console.log('done and return');
71+
await processResourcesAndSchemas(config, resourceTypes, schemas);
7472
return { success: true };
7573
} catch (error) {
7674
console.error('Error fetching SCIM resources:', error);
@@ -114,4 +112,13 @@ runAllTests(
114112
authHeader: process.env.AUTH_HEADER,
115113
...parse(process.env.CONFIG),
116114
}
117-
);
115+
).then((result) => {
116+
if (!result.success) {
117+
process.exit(1);
118+
}
119+
}).catch((error) => {
120+
console.error('Error running tests:', error);
121+
process.exit(1);
122+
}).finally(() => {
123+
writeHarFile('scim');
124+
});

server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ async function processQueue() {
5353
process.env.BASE_URL = req.body.url;
5454
process.env.AUTH_HEADER = req.body.authHeader;
5555
process.env.CONFIG = JSON.stringify(req.body);
56+
process.env.HAR_VIA_DIAGNOSTIC = true;
5657

5758
const responseStream = new Writable({
5859
write(chunk, encoding, callback) {

site/.vitepress/theme/components/Report.vue

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,12 @@
9292
<div class="test-output" ref="testOutputSection" v-if="testFiles?.length > 0 || isRunningTests">
9393
<h2>
9494
Test Results
95-
<div v-if="isRunningTests" class="loading-spinner">
96-
<div class="spinner"></div>
97-
<span>Running tests...</span>
95+
<button v-if="!isRunningTests && harMessages.length > 0" @click="downloadHar" class="download-button">
96+
Download HTTP Archive (HAR)
97+
</button>
98+
<div v-if="isRunningTests" class="loading-spinner">
99+
<div class="spinner"></div>
100+
<span>Running tests...</span>
98101
</div>
99102
</h2>
100103
<div v-if="isRunningTests && testFiles.length === 0" class="running-tests-message">
@@ -110,7 +113,7 @@
110113
r.skipped() ? 'test:skip' : '',
111114
!(r.getLatest().type === 'test:fail' ||
112115
r.skipped() ||
113-
r.messages.some(m => m.type === 'test:diagnostic' && m.data.message?.type === 'request')) ? 'no-details' : ''
116+
r.messages.some(m => m.type === 'test:diagnostic' && m.data.message?._type === 'har')) ? 'no-details' : ''
114117
]">
115118
<component :is="r.getLatest().data.nesting == 0 ? 'h2' : 'h3'">
116119
{{ r.getLatest().data.name }} <span>show more ↴</span>
@@ -125,7 +128,7 @@
125128

126129
<!-- Diagnostic Messages -->
127130
<div v-else-if="r.messages.filter(m => ['test:diagnostic'].includes(m.type)).length > 0"
128-
v-for="msg in r?.messages.filter(m => ['test:diagnostic'].includes(m.type) && m.data.message.type == 'request')"
131+
v-for="msg in r?.messages.filter(m => ['test:diagnostic'].includes(m.type) && m.data.message._type == 'har')"
129132
class="diagnostic-tabs">
130133
<div class="tabs">
131134
<div class="tab" :class="{ active: getActiveDiagnosticTab(msg.id) === 'request' }"
@@ -140,32 +143,26 @@
140143

141144
<div v-if="getActiveDiagnosticTab(msg.id) === 'request'" class="tab-content">
142145
<div class="http-request">
143-
<div class="request-line"><strong>{{ msg.data.message.method }}</strong> {{ msg.data.message.url }}
146+
<div class="request-line"><strong>{{ msg.data.message.request.method }}</strong> {{ msg.data.message.request.url }}
144147
HTTP/1.1</div>
145148
<div class="request-headers">
146-
<div v-for="(value, key) in msg.data.message.headers" :key="key">
147-
<strong>{{ key }}:</strong> {{ value }}
149+
<div v-for="header in msg.data.message.request.headers" :key="key">
150+
<strong>{{ header.name }}:</strong> {{ header.value }}
148151
</div>
149152
</div>
150-
<pre v-if="msg.data.message.body">{{ formatJSON(msg.data.message.body) }}</pre>
153+
<pre v-if="msg.data.message.request.postData">{{ formatJSON(msg.data.message.request.postData.text) }}</pre>
151154
</div>
152155
</div>
153156

154157
<div v-if="getActiveDiagnosticTab(msg.id) === 'response'" class="tab-content">
155-
<div v-for="response in r?.messages.filter((m, index) => {
156-
const msgIndex = r.messages.findIndex(item => item.id === msg.id);
157-
return ['test:diagnostic'].includes(m.type) &&
158-
m.data.message.type == 'response' &&
159-
m.data.message.requestId === msg.data.message.id &&
160-
index > msgIndex;
161-
}).slice(0, 1)">
162-
<div>HTTP {{ response.data.message.status }} {{ response.data.message.statusText }}</div>
163-
<div v-if="response.data.message.headers">
164-
<span v-for="(value, key) in response.data.message.headers" :key="key">
165-
<strong>{{ key }}:</strong> {{ value }}<br />
166-
</span>
158+
<div class="http-response">
159+
<div class="status-line">HTTP {{ msg.data.message.response.status }} {{ msg.data.message.response.statusText }}</div>
160+
<div class="response-headers" v-if="msg.data.message.response.headers">
161+
<div v-for="header in msg.data.message.response.headers" :key="header.name">
162+
<strong>{{ header.name }}:</strong> {{ header.value }}
163+
</div>
167164
</div>
168-
<pre v-if="response.data.message.body">{{ formatJSON(response.data.message.body) }}</pre>
165+
<pre v-if="msg.data.message.response.content">{{ formatJSON(msg.data.message.response.content.text) }}</pre>
169166
</div>
170167
</div>
171168
</div>
@@ -187,6 +184,7 @@ import { foldGutter, foldKeymap } from '@codemirror/language';
187184
import { keymap } from '@codemirror/view';
188185
import defaultConfig from './config.yaml?raw';
189186
187+
190188
class TestResult {
191189
constructor(file, line, column, name) {
192190
this.file = file;
@@ -271,6 +269,7 @@ export default {
271269
turnstileWidgetId: null,
272270
273271
isRunningTests: false, // Track if tests are currently running
272+
harMessages: [], // Add this to store HAR messages
274273
};
275274
},
276275
created() {
@@ -489,6 +488,7 @@ export default {
489488
this.result.clear();
490489
this.testFiles = [];
491490
this.diagnosticTabs.clear();
491+
this.harMessages = []; // Clear HAR messages
492492
this.output = 'Starting tests...\n';
493493
494494
const serverUrl = import.meta.env.VITE_SCIM_TEST_SERVER_URL;
@@ -592,9 +592,14 @@ export default {
592592
let existing = testFile.findResult(
593593
json.data.line,
594594
json.data.column,
595-
json.data.name ?? json.data.message?.test_name
595+
json.data.name ?? json.data.message?.test_name ?? json.data.message?._test_name
596596
);
597597
598+
// Check if this is a HAR message that we need to store
599+
if (json.data?.message?._type === 'har') {
600+
this.harMessages.push(json);
601+
}
602+
598603
if (existing) {
599604
existing.messages.push(json);
600605
} else {
@@ -672,7 +677,7 @@ export default {
672677
},
673678
674679
formatJSON(json) {
675-
return JSON.stringify(json, null, 2);
680+
return JSON.stringify(JSON.parse(json), null, 2);
676681
},
677682
// Set active tab for a specific diagnostic message
678683
setDiagnosticTab(messageId, tabName) {
@@ -684,6 +689,31 @@ export default {
684689
// Default to 'response' if not set
685690
return this.diagnosticTabs.get(messageId) || 'response';
686691
},
692+
693+
downloadHar() {
694+
const har = {
695+
log: {
696+
version: '1.2',
697+
creator: {
698+
name: 'verify.scim.dev',
699+
version: '1.0'
700+
},
701+
pages: [],
702+
entries: this.harMessages.map(msg => msg.data.message)
703+
}
704+
};
705+
706+
// Create blob and download
707+
const blob = new Blob([JSON.stringify(har, null, 2)], { type: 'application/json' });
708+
const url = URL.createObjectURL(blob);
709+
const a = document.createElement('a');
710+
a.href = url;
711+
a.download = 'scim-verify.har';
712+
document.body.appendChild(a);
713+
a.click();
714+
document.body.removeChild(a);
715+
URL.revokeObjectURL(url);
716+
},
687717
},
688718
};
689719
</script>
@@ -1124,7 +1154,7 @@ summary {
11241154
11251155
/* Code styling */
11261156
pre,
1127-
.http-request {
1157+
.http-request, .http-response {
11281158
font-family: 'SF Mono', SFMono-Regular, ui-monospace, Consolas, Menlo, monospace;
11291159
font-size: 13px;
11301160
}
@@ -1150,7 +1180,7 @@ pre {
11501180
}
11511181
}
11521182
1153-
.request-headers {
1183+
.response-headers, .request-headers {
11541184
margin-bottom: 12px;
11551185
color: #64748b;
11561186
@@ -1443,4 +1473,28 @@ select {
14431473
color: #1a73e8;
14441474
text-decoration: underline !important;
14451475
}
1476+
1477+
1478+
.download-button {
1479+
background-color: transparent;
1480+
color: #1a73e8;
1481+
padding: 6px 12px;
1482+
border: 1px solid #1a73e8;
1483+
border-radius: 4px;
1484+
cursor: pointer;
1485+
font-size: 12px;
1486+
font-weight: 500;
1487+
margin: 0;
1488+
margin-left: 10px;
1489+
transition: all 0.15s ease;
1490+
1491+
&:hover {
1492+
background-color: rgba(26, 115, 232, 0.04);
1493+
text-decoration: none;
1494+
}
1495+
1496+
&:active {
1497+
background-color: rgba(26, 115, 232, 0.08);
1498+
}
1499+
}
14461500
</style>

src/basics.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import assert from 'node:assert';
33
import { getAxiosInstance } from './helpers.js';
44

55
function runTests(config) {
6-
7-
test.describe('Basic tests', function () {
6+
return test.describe('Basic tests', function () {
87
test('Base URL should not contain any query parameters', function () {
98
const baseUrl = config.baseURL;
109
const url = new URL(baseUrl);

src/groups.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async function lookupUserId(configuration, t){
4444
}
4545

4646
function runTests(groupSchema, groupSchemaExtensions = [], configuration) {
47-
test.describe('Groups', () => {
47+
return test.describe('Groups', async () => {
4848
test('groupSchema contains attribute displayName and it is marked as required', () => {
4949
const displayNameAttribute = groupSchema.attributes.find(attr => attr.name === 'displayName');
5050
assert.ok(displayNameAttribute, 'displayName attribute should exist in groupSchema');

0 commit comments

Comments
 (0)