javascript-solid-server 0.0.2 → 0.0.5

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.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Pod lifecycle tests
3
+ */
4
+
5
+ import { describe, it, before, after } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import {
8
+ startTestServer,
9
+ stopTestServer,
10
+ request,
11
+ createTestPod,
12
+ assertStatus,
13
+ assertHeader,
14
+ assertHeaderContains
15
+ } from './helpers.js';
16
+
17
+ describe('Pod Lifecycle', () => {
18
+ before(async () => {
19
+ await startTestServer();
20
+ });
21
+
22
+ after(async () => {
23
+ await stopTestServer();
24
+ });
25
+
26
+ describe('POST /.pods', () => {
27
+ it('should create a new pod', async () => {
28
+ const res = await request('/.pods', {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ name: 'alice' })
32
+ });
33
+
34
+ assertStatus(res, 201);
35
+ assertHeader(res, 'Location');
36
+
37
+ const data = await res.json();
38
+ assert.strictEqual(data.name, 'alice');
39
+ assert.ok(data.webId.endsWith('/alice/#me'));
40
+ assert.ok(data.podUri.endsWith('/alice/'));
41
+ });
42
+
43
+ it('should reject duplicate pod names', async () => {
44
+ // First create
45
+ await request('/.pods', {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ name: 'bob' })
49
+ });
50
+
51
+ // Duplicate attempt
52
+ const res = await request('/.pods', {
53
+ method: 'POST',
54
+ headers: { 'Content-Type': 'application/json' },
55
+ body: JSON.stringify({ name: 'bob' })
56
+ });
57
+
58
+ assertStatus(res, 409);
59
+ });
60
+
61
+ it('should reject invalid pod names', async () => {
62
+ const res = await request('/.pods', {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ name: '../evil' })
66
+ });
67
+
68
+ assertStatus(res, 400);
69
+ });
70
+
71
+ it('should reject empty pod name', async () => {
72
+ const res = await request('/.pods', {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify({})
76
+ });
77
+
78
+ assertStatus(res, 400);
79
+ });
80
+ });
81
+
82
+ describe('Pod Structure', () => {
83
+ it('should create standard folders', async () => {
84
+ await createTestPod('carol');
85
+
86
+ // Check inbox exists (needs auth - inbox only allows public append, not read)
87
+ const inbox = await request('/carol/inbox/', { auth: 'carol' });
88
+ assertStatus(inbox, 200);
89
+
90
+ // Check public exists (public read via root ACL default)
91
+ const pub = await request('/carol/public/');
92
+ assertStatus(pub, 200);
93
+
94
+ // Check private exists (needs auth)
95
+ const priv = await request('/carol/private/', { auth: 'carol' });
96
+ assertStatus(priv, 200);
97
+
98
+ // Check settings exists (needs auth)
99
+ const settings = await request('/carol/settings/', { auth: 'carol' });
100
+ assertStatus(settings, 200);
101
+ });
102
+
103
+ it('should create settings files', async () => {
104
+ await createTestPod('dan');
105
+
106
+ // Check prefs (needs auth - settings is private)
107
+ const prefs = await request('/dan/settings/prefs', { auth: 'dan' });
108
+ assertStatus(prefs, 200);
109
+
110
+ // Check public type index (needs auth)
111
+ const pubIndex = await request('/dan/settings/publicTypeIndex', { auth: 'dan' });
112
+ assertStatus(pubIndex, 200);
113
+
114
+ // Check private type index (needs auth)
115
+ const privIndex = await request('/dan/settings/privateTypeIndex', { auth: 'dan' });
116
+ assertStatus(privIndex, 200);
117
+ });
118
+ });
119
+ });
@@ -0,0 +1,189 @@
1
+ /**
2
+ * WAC (Web Access Control) tests
3
+ */
4
+
5
+ import { describe, it, before, after } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import {
8
+ startTestServer,
9
+ stopTestServer,
10
+ request,
11
+ createTestPod,
12
+ assertStatus,
13
+ assertHeader,
14
+ getBaseUrl
15
+ } from './helpers.js';
16
+ import { parseAcl, AccessMode, generateOwnerAcl, serializeAcl } from '../src/wac/parser.js';
17
+ import { checkAccess, getRequiredMode } from '../src/wac/checker.js';
18
+
19
+ describe('WAC Parser', () => {
20
+ describe('parseAcl', () => {
21
+ it('should parse a simple ACL', () => {
22
+ const acl = {
23
+ '@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
24
+ '@graph': [{
25
+ '@id': '#owner',
26
+ '@type': 'acl:Authorization',
27
+ 'acl:agent': { '@id': 'https://alice.example/#me' },
28
+ 'acl:accessTo': { '@id': 'https://alice.example/resource' },
29
+ 'acl:mode': [{ '@id': 'acl:Read' }, { '@id': 'acl:Write' }]
30
+ }]
31
+ };
32
+
33
+ const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/.acl');
34
+
35
+ assert.strictEqual(auths.length, 1);
36
+ assert.ok(auths[0].agents.includes('https://alice.example/#me'));
37
+ assert.ok(auths[0].modes.includes(AccessMode.READ));
38
+ assert.ok(auths[0].modes.includes(AccessMode.WRITE));
39
+ });
40
+
41
+ it('should parse public access', () => {
42
+ const acl = {
43
+ '@context': { 'acl': 'http://www.w3.org/ns/auth/acl#', 'foaf': 'http://xmlns.com/foaf/0.1/' },
44
+ '@graph': [{
45
+ '@id': '#public',
46
+ '@type': 'acl:Authorization',
47
+ 'acl:agentClass': { '@id': 'foaf:Agent' },
48
+ 'acl:accessTo': { '@id': 'https://alice.example/public/' },
49
+ 'acl:mode': [{ '@id': 'acl:Read' }]
50
+ }]
51
+ };
52
+
53
+ const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/public/.acl');
54
+
55
+ assert.strictEqual(auths.length, 1);
56
+ assert.ok(auths[0].agentClasses.includes('foaf:Agent'));
57
+ assert.ok(auths[0].modes.includes(AccessMode.READ));
58
+ });
59
+
60
+ it('should parse default authorizations for containers', () => {
61
+ const acl = {
62
+ '@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
63
+ '@graph': [{
64
+ '@id': '#default',
65
+ '@type': 'acl:Authorization',
66
+ 'acl:agent': { '@id': 'https://alice.example/#me' },
67
+ 'acl:default': { '@id': 'https://alice.example/folder/' },
68
+ 'acl:mode': [{ '@id': 'acl:Read' }]
69
+ }]
70
+ };
71
+
72
+ const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/folder/.acl');
73
+
74
+ assert.strictEqual(auths.length, 1);
75
+ assert.ok(auths[0].default.includes('https://alice.example/folder/'));
76
+ });
77
+
78
+ it('should handle invalid JSON gracefully', () => {
79
+ const auths = parseAcl('not valid json', 'https://example.com/.acl');
80
+ assert.strictEqual(auths.length, 0);
81
+ });
82
+ });
83
+
84
+ describe('generateOwnerAcl', () => {
85
+ it('should generate owner ACL with public read', () => {
86
+ const acl = generateOwnerAcl('https://alice.example/', 'https://alice.example/#me', true);
87
+
88
+ assert.ok(acl['@graph'].length >= 2);
89
+
90
+ // Find owner auth
91
+ const ownerAuth = acl['@graph'].find(a => a['@id'] === '#owner');
92
+ assert.ok(ownerAuth);
93
+ assert.strictEqual(ownerAuth['acl:agent']['@id'], 'https://alice.example/#me');
94
+
95
+ // Find public auth
96
+ const publicAuth = acl['@graph'].find(a => a['@id'] === '#public');
97
+ assert.ok(publicAuth);
98
+ });
99
+ });
100
+ });
101
+
102
+ describe('WAC Checker', () => {
103
+ describe('getRequiredMode', () => {
104
+ it('should return READ for GET', () => {
105
+ assert.strictEqual(getRequiredMode('GET'), AccessMode.READ);
106
+ });
107
+
108
+ it('should return READ for HEAD', () => {
109
+ assert.strictEqual(getRequiredMode('HEAD'), AccessMode.READ);
110
+ });
111
+
112
+ it('should return APPEND for POST', () => {
113
+ assert.strictEqual(getRequiredMode('POST'), AccessMode.APPEND);
114
+ });
115
+
116
+ it('should return WRITE for PUT', () => {
117
+ assert.strictEqual(getRequiredMode('PUT'), AccessMode.WRITE);
118
+ });
119
+
120
+ it('should return WRITE for DELETE', () => {
121
+ assert.strictEqual(getRequiredMode('DELETE'), AccessMode.WRITE);
122
+ });
123
+ });
124
+ });
125
+
126
+ describe('WAC Integration', () => {
127
+ let baseUrl;
128
+
129
+ before(async () => {
130
+ const result = await startTestServer();
131
+ baseUrl = result.baseUrl;
132
+ await createTestPod('wactest');
133
+ });
134
+
135
+ after(async () => {
136
+ await stopTestServer();
137
+ });
138
+
139
+ describe('ACL Files', () => {
140
+ it('should create root .acl on pod creation', async () => {
141
+ const res = await request('/wactest/.acl');
142
+
143
+ assertStatus(res, 200);
144
+ const content = await res.json();
145
+ assert.ok(content['@graph'], 'Should be JSON-LD');
146
+ });
147
+
148
+ it('should create private folder .acl', async () => {
149
+ const res = await request('/wactest/private/.acl');
150
+
151
+ assertStatus(res, 200);
152
+ const content = await res.json();
153
+ assert.ok(content['@graph']);
154
+
155
+ // Should only have owner, no public
156
+ const hasPublic = content['@graph'].some(a =>
157
+ a['acl:agentClass'] && a['acl:agentClass']['@id'] === 'foaf:Agent'
158
+ );
159
+ assert.ok(!hasPublic, 'Private folder should not have public access');
160
+ });
161
+
162
+ it('should create inbox .acl with public append', async () => {
163
+ const res = await request('/wactest/inbox/.acl');
164
+
165
+ assertStatus(res, 200);
166
+ const content = await res.json();
167
+
168
+ // Should have public append
169
+ const publicAuth = content['@graph'].find(a =>
170
+ a['acl:agentClass'] && a['acl:agentClass']['@id'] === 'foaf:Agent'
171
+ );
172
+ assert.ok(publicAuth, 'Inbox should have public access');
173
+
174
+ const modes = publicAuth['acl:mode'].map(m => m['@id']);
175
+ assert.ok(modes.includes('acl:Append'), 'Public should have Append');
176
+ assert.ok(!modes.includes('acl:Read'), 'Public should not have Read');
177
+ });
178
+ });
179
+
180
+ describe('WAC-Allow Header', () => {
181
+ it('should return WAC-Allow header for public container', async () => {
182
+ const res = await request('/wactest/public/');
183
+
184
+ assertHeader(res, 'WAC-Allow');
185
+ const wacAllow = res.headers.get('WAC-Allow');
186
+ assert.ok(wacAllow.includes('public='), 'Should have public permissions');
187
+ });
188
+ });
189
+ });
@@ -0,0 +1,152 @@
1
+ /**
2
+ * WebID Profile tests
3
+ */
4
+
5
+ import { describe, it, before, after } from 'node:test';
6
+ import assert from 'node:assert';
7
+ import {
8
+ startTestServer,
9
+ stopTestServer,
10
+ request,
11
+ createTestPod,
12
+ assertStatus,
13
+ assertHeader,
14
+ assertHeaderContains,
15
+ extractJsonLdFromHtml
16
+ } from './helpers.js';
17
+
18
+ describe('WebID Profile', () => {
19
+ let baseUrl;
20
+ let podInfo;
21
+
22
+ before(async () => {
23
+ const result = await startTestServer();
24
+ baseUrl = result.baseUrl;
25
+ podInfo = await createTestPod('webidtest');
26
+ });
27
+
28
+ after(async () => {
29
+ await stopTestServer();
30
+ });
31
+
32
+ describe('Profile Document', () => {
33
+ it('should serve profile as HTML at pod root', async () => {
34
+ const res = await request('/webidtest/');
35
+
36
+ assertStatus(res, 200);
37
+ assertHeaderContains(res, 'Content-Type', 'text/html');
38
+ });
39
+
40
+ it('should contain JSON-LD structured data', async () => {
41
+ const res = await request('/webidtest/');
42
+ const html = await res.text();
43
+
44
+ const jsonLd = extractJsonLdFromHtml(html);
45
+ assert.ok(jsonLd['@context'], 'Should have @context');
46
+ assert.ok(jsonLd['@graph'], 'Should have @graph');
47
+ });
48
+
49
+ it('should have correct WebID URI', async () => {
50
+ const res = await request('/webidtest/');
51
+ const html = await res.text();
52
+ const jsonLd = extractJsonLdFromHtml(html);
53
+
54
+ // Find the Person in the graph
55
+ const person = jsonLd['@graph'].find(node =>
56
+ Array.isArray(node['@type'])
57
+ ? node['@type'].includes('foaf:Person')
58
+ : node['@type'] === 'foaf:Person'
59
+ );
60
+
61
+ assert.ok(person, 'Should have a foaf:Person');
62
+ assert.ok(person['@id'].endsWith('/webidtest/#me'), 'WebID should end with /#me');
63
+ });
64
+
65
+ it('should have foaf:name', async () => {
66
+ const res = await request('/webidtest/');
67
+ const html = await res.text();
68
+ const jsonLd = extractJsonLdFromHtml(html);
69
+
70
+ const person = jsonLd['@graph'].find(node =>
71
+ Array.isArray(node['@type'])
72
+ ? node['@type'].includes('foaf:Person')
73
+ : node['@type'] === 'foaf:Person'
74
+ );
75
+
76
+ assert.strictEqual(person['foaf:name'], 'webidtest');
77
+ });
78
+
79
+ it('should have solid:oidcIssuer', async () => {
80
+ const res = await request('/webidtest/');
81
+ const html = await res.text();
82
+ const jsonLd = extractJsonLdFromHtml(html);
83
+
84
+ const person = jsonLd['@graph'].find(node =>
85
+ Array.isArray(node['@type'])
86
+ ? node['@type'].includes('foaf:Person')
87
+ : node['@type'] === 'foaf:Person'
88
+ );
89
+
90
+ assert.ok(person['oidcIssuer'], 'Should have oidcIssuer');
91
+ });
92
+
93
+ it('should have pim:storage pointing to pod', async () => {
94
+ const res = await request('/webidtest/');
95
+ const html = await res.text();
96
+ const jsonLd = extractJsonLdFromHtml(html);
97
+
98
+ const person = jsonLd['@graph'].find(node =>
99
+ Array.isArray(node['@type'])
100
+ ? node['@type'].includes('foaf:Person')
101
+ : node['@type'] === 'foaf:Person'
102
+ );
103
+
104
+ assert.ok(person['storage'].endsWith('/webidtest/'), 'Storage should point to pod');
105
+ });
106
+
107
+ it('should have ldp:inbox', async () => {
108
+ const res = await request('/webidtest/');
109
+ const html = await res.text();
110
+ const jsonLd = extractJsonLdFromHtml(html);
111
+
112
+ const person = jsonLd['@graph'].find(node =>
113
+ Array.isArray(node['@type'])
114
+ ? node['@type'].includes('foaf:Person')
115
+ : node['@type'] === 'foaf:Person'
116
+ );
117
+
118
+ assert.ok(person['inbox'].endsWith('/webidtest/inbox/'), 'Should have inbox');
119
+ });
120
+
121
+ it('should have PersonalProfileDocument', async () => {
122
+ const res = await request('/webidtest/');
123
+ const html = await res.text();
124
+ const jsonLd = extractJsonLdFromHtml(html);
125
+
126
+ const doc = jsonLd['@graph'].find(node =>
127
+ node['@type'] === 'foaf:PersonalProfileDocument'
128
+ );
129
+
130
+ assert.ok(doc, 'Should have PersonalProfileDocument');
131
+ assert.ok(doc['foaf:maker'], 'Should have foaf:maker');
132
+ assert.ok(doc['foaf:primaryTopic'], 'Should have foaf:primaryTopic');
133
+ });
134
+ });
135
+
136
+ describe('WebID Resolution', () => {
137
+ it('should return LDP headers', async () => {
138
+ const res = await request('/webidtest/');
139
+
140
+ assertHeaderContains(res, 'Link', 'ldp#Resource');
141
+ assertHeader(res, 'WAC-Allow');
142
+ });
143
+
144
+ it('should return CORS headers', async () => {
145
+ const res = await request('/webidtest/', {
146
+ headers: { 'Origin': 'https://example.com' }
147
+ });
148
+
149
+ assertHeader(res, 'Access-Control-Allow-Origin');
150
+ });
151
+ });
152
+ });
@@ -1,44 +0,0 @@
1
- {
2
- "timestamp": "2025-03-31T14:25:24.234Z",
3
- "server": "http://nostr.social:3000",
4
- "testDuration": 30000,
5
- "averageResponseTimes": {
6
- "register": 49.28774029878249,
7
- "login": 49.07647125548627,
8
- "read": 145.50252931779679,
9
- "write": 143.20483738812226,
10
- "delete": 145.5004399698014
11
- },
12
- "throughputResults": [
13
- {
14
- "concurrentUsers": 1,
15
- "operations": 300,
16
- "duration": 629.3636230230331,
17
- "throughput": 476.67197312581374
18
- },
19
- {
20
- "concurrentUsers": 5,
21
- "operations": 1500,
22
- "duration": 3072.7389999628067,
23
- "throughput": 488.16381736885444
24
- },
25
- {
26
- "concurrentUsers": 10,
27
- "operations": 3000,
28
- "duration": 6042.516402959824,
29
- "throughput": 496.4818959416479
30
- },
31
- {
32
- "concurrentUsers": 50,
33
- "operations": 14000,
34
- "duration": 30000,
35
- "throughput": 466.6666666666667
36
- },
37
- {
38
- "concurrentUsers": 100,
39
- "operations": 13900,
40
- "duration": 30000,
41
- "throughput": 463.3333333333333
42
- }
43
- ]
44
- }