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.
- package/.claude/settings.local.json +5 -1
- package/package.json +2 -2
- package/src/auth/middleware.js +97 -0
- package/src/auth/token.js +112 -0
- package/src/handlers/container.js +71 -7
- package/src/handlers/resource.js +39 -9
- package/src/ldp/headers.js +31 -6
- package/src/server.js +23 -1
- package/src/wac/checker.js +257 -0
- package/src/wac/parser.js +284 -0
- package/src/webid/profile.js +161 -0
- package/test/auth.test.js +175 -0
- package/test/helpers.js +158 -0
- package/test/ldp.test.js +363 -0
- package/test/pod.test.js +119 -0
- package/test/wac.test.js +189 -0
- package/test/webid.test.js +152 -0
- package/benchmark-report-2025-03-31T14-25-24.234Z.json +0 -44
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and Authorization 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
|
+
getPodToken,
|
|
13
|
+
getBaseUrl,
|
|
14
|
+
assertStatus,
|
|
15
|
+
assertHeader
|
|
16
|
+
} from './helpers.js';
|
|
17
|
+
|
|
18
|
+
describe('Authentication', () => {
|
|
19
|
+
before(async () => {
|
|
20
|
+
await startTestServer();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
after(async () => {
|
|
24
|
+
await stopTestServer();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Token Authentication', () => {
|
|
28
|
+
it('should return token on pod creation', async () => {
|
|
29
|
+
const result = await createTestPod('authtest');
|
|
30
|
+
|
|
31
|
+
assert.ok(result.token, 'Should return a token');
|
|
32
|
+
assert.ok(result.token.includes('.'), 'Token should have signature');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should allow authenticated access to private resources', async () => {
|
|
36
|
+
await createTestPod('privatetest');
|
|
37
|
+
|
|
38
|
+
// Should succeed with auth
|
|
39
|
+
const res = await request('/privatetest/private/', { auth: 'privatetest' });
|
|
40
|
+
assertStatus(res, 200);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should deny unauthenticated access to private resources', async () => {
|
|
44
|
+
await createTestPod('denytest');
|
|
45
|
+
|
|
46
|
+
// Should fail without auth
|
|
47
|
+
const res = await request('/denytest/private/');
|
|
48
|
+
assertStatus(res, 401);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return 403 for wrong user accessing private resources', async () => {
|
|
52
|
+
await createTestPod('user1');
|
|
53
|
+
await createTestPod('user2');
|
|
54
|
+
|
|
55
|
+
// User2 trying to access User1's private folder
|
|
56
|
+
const res = await request('/user1/private/', { auth: 'user2' });
|
|
57
|
+
assertStatus(res, 403);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should accept Bearer token format', async () => {
|
|
61
|
+
await createTestPod('bearertest');
|
|
62
|
+
const token = getPodToken('bearertest');
|
|
63
|
+
|
|
64
|
+
const res = await fetch(`${getBaseUrl()}/bearertest/private/`, {
|
|
65
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
66
|
+
});
|
|
67
|
+
assertStatus(res, 200);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should reject invalid tokens', async () => {
|
|
71
|
+
await createTestPod('invalidtest');
|
|
72
|
+
|
|
73
|
+
const res = await fetch(`${getBaseUrl()}/invalidtest/private/`, {
|
|
74
|
+
headers: { 'Authorization': 'Bearer invalid.token' }
|
|
75
|
+
});
|
|
76
|
+
assertStatus(res, 401);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('WAC Enforcement', () => {
|
|
81
|
+
it('should allow public read on pod root', async () => {
|
|
82
|
+
await createTestPod('publicread');
|
|
83
|
+
|
|
84
|
+
// Public folder should be readable without auth
|
|
85
|
+
const res = await request('/publicread/public/');
|
|
86
|
+
assertStatus(res, 200);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should allow public read on explicit public folders', async () => {
|
|
90
|
+
await createTestPod('explicitpublic');
|
|
91
|
+
|
|
92
|
+
// Root ACL has public read default
|
|
93
|
+
const res = await request('/explicitpublic/');
|
|
94
|
+
assertStatus(res, 200);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should allow authenticated write to owned resources', async () => {
|
|
98
|
+
await createTestPod('writetest');
|
|
99
|
+
|
|
100
|
+
const res = await request('/writetest/public/test.txt', {
|
|
101
|
+
method: 'PUT',
|
|
102
|
+
body: 'test content',
|
|
103
|
+
auth: 'writetest'
|
|
104
|
+
});
|
|
105
|
+
assertStatus(res, 201);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should deny unauthenticated write', async () => {
|
|
109
|
+
await createTestPod('nowrite');
|
|
110
|
+
|
|
111
|
+
const res = await request('/nowrite/public/test.txt', {
|
|
112
|
+
method: 'PUT',
|
|
113
|
+
body: 'test content'
|
|
114
|
+
});
|
|
115
|
+
assertStatus(res, 401);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should deny other user write to owned resources', async () => {
|
|
119
|
+
await createTestPod('owner1');
|
|
120
|
+
await createTestPod('attacker');
|
|
121
|
+
|
|
122
|
+
const res = await request('/owner1/public/test.txt', {
|
|
123
|
+
method: 'PUT',
|
|
124
|
+
body: 'malicious content',
|
|
125
|
+
auth: 'attacker'
|
|
126
|
+
});
|
|
127
|
+
assertStatus(res, 403);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should allow public append to inbox', async () => {
|
|
131
|
+
await createTestPod('inboxtest');
|
|
132
|
+
|
|
133
|
+
// POST to inbox should work for anyone (public append)
|
|
134
|
+
const res = await request('/inboxtest/inbox/', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
'Slug': 'notification'
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({ type: 'notification' })
|
|
141
|
+
});
|
|
142
|
+
assertStatus(res, 201);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should deny public read on inbox', async () => {
|
|
146
|
+
await createTestPod('inboxread');
|
|
147
|
+
|
|
148
|
+
// GET inbox should fail for unauthenticated
|
|
149
|
+
const res = await request('/inboxread/inbox/');
|
|
150
|
+
assertStatus(res, 401);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('WAC-Allow Header', () => {
|
|
155
|
+
it('should include user permissions for authenticated requests', async () => {
|
|
156
|
+
await createTestPod('wacallow');
|
|
157
|
+
|
|
158
|
+
const res = await request('/wacallow/public/', { auth: 'wacallow' });
|
|
159
|
+
const wacAllow = res.headers.get('WAC-Allow');
|
|
160
|
+
|
|
161
|
+
assert.ok(wacAllow, 'Should have WAC-Allow header');
|
|
162
|
+
assert.ok(wacAllow.includes('user='), 'Should include user permissions');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should include public permissions', async () => {
|
|
166
|
+
await createTestPod('wacpublic');
|
|
167
|
+
|
|
168
|
+
const res = await request('/wacpublic/public/');
|
|
169
|
+
const wacAllow = res.headers.get('WAC-Allow');
|
|
170
|
+
|
|
171
|
+
assert.ok(wacAllow, 'Should have WAC-Allow header');
|
|
172
|
+
assert.ok(wacAllow.includes('public='), 'Should include public permissions');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
package/test/helpers.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for JavaScript Solid Server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createServer } from '../src/server.js';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const TEST_DATA_DIR = './data';
|
|
10
|
+
|
|
11
|
+
let server = null;
|
|
12
|
+
let baseUrl = null;
|
|
13
|
+
|
|
14
|
+
// Store tokens for pods by name
|
|
15
|
+
const podTokens = new Map();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Start a test server on a random available port
|
|
19
|
+
* @returns {Promise<{server: object, baseUrl: string}>}
|
|
20
|
+
*/
|
|
21
|
+
export async function startTestServer() {
|
|
22
|
+
// Clean up any existing test data
|
|
23
|
+
await fs.emptyDir(TEST_DATA_DIR);
|
|
24
|
+
|
|
25
|
+
server = createServer({ logger: false });
|
|
26
|
+
// Use port 0 to let OS assign available port
|
|
27
|
+
await server.listen({ port: 0, host: '127.0.0.1' });
|
|
28
|
+
|
|
29
|
+
const address = server.server.address();
|
|
30
|
+
baseUrl = `http://127.0.0.1:${address.port}`;
|
|
31
|
+
|
|
32
|
+
return { server, baseUrl };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Stop the test server
|
|
37
|
+
*/
|
|
38
|
+
export async function stopTestServer() {
|
|
39
|
+
if (server) {
|
|
40
|
+
await server.close();
|
|
41
|
+
server = null;
|
|
42
|
+
}
|
|
43
|
+
// Clean up test data
|
|
44
|
+
await fs.emptyDir(TEST_DATA_DIR);
|
|
45
|
+
// Clear tokens
|
|
46
|
+
podTokens.clear();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the base URL
|
|
51
|
+
*/
|
|
52
|
+
export function getBaseUrl() {
|
|
53
|
+
return baseUrl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a pod for testing
|
|
58
|
+
* @param {string} name - Pod name
|
|
59
|
+
* @returns {Promise<{webId: string, podUri: string, token: string}>}
|
|
60
|
+
*/
|
|
61
|
+
export async function createTestPod(name) {
|
|
62
|
+
const res = await fetch(`${baseUrl}/.pods`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
body: JSON.stringify({ name })
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
throw new Error(`Failed to create pod: ${res.status}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await res.json();
|
|
73
|
+
|
|
74
|
+
// Store the token for this pod
|
|
75
|
+
if (result.token) {
|
|
76
|
+
podTokens.set(name, result.token);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get token for a pod
|
|
84
|
+
* @param {string} name - Pod name
|
|
85
|
+
* @returns {string|null}
|
|
86
|
+
*/
|
|
87
|
+
export function getPodToken(name) {
|
|
88
|
+
return podTokens.get(name) || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Make a request to the test server
|
|
93
|
+
* @param {string} path - URL path
|
|
94
|
+
* @param {object} options - fetch options (can include `auth: 'podname'` for authenticated requests)
|
|
95
|
+
* @returns {Promise<Response>}
|
|
96
|
+
*/
|
|
97
|
+
export async function request(urlPath, options = {}) {
|
|
98
|
+
const url = urlPath.startsWith('http') ? urlPath : `${baseUrl}${urlPath}`;
|
|
99
|
+
|
|
100
|
+
// Handle authentication
|
|
101
|
+
const { auth, ...fetchOptions } = options;
|
|
102
|
+
if (auth) {
|
|
103
|
+
const token = podTokens.get(auth);
|
|
104
|
+
if (token) {
|
|
105
|
+
fetchOptions.headers = {
|
|
106
|
+
...fetchOptions.headers,
|
|
107
|
+
'Authorization': `Bearer ${token}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return fetch(url, fetchOptions);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Assert response status
|
|
117
|
+
*/
|
|
118
|
+
export function assertStatus(res, expected, message = '') {
|
|
119
|
+
if (res.status !== expected) {
|
|
120
|
+
throw new Error(`Expected status ${expected}, got ${res.status}. ${message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Assert response header exists
|
|
126
|
+
*/
|
|
127
|
+
export function assertHeader(res, header, expected = undefined) {
|
|
128
|
+
const value = res.headers.get(header);
|
|
129
|
+
if (value === null) {
|
|
130
|
+
throw new Error(`Expected header ${header} to exist`);
|
|
131
|
+
}
|
|
132
|
+
if (expected !== undefined && value !== expected) {
|
|
133
|
+
throw new Error(`Expected header ${header} to be "${expected}", got "${value}"`);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Assert response header contains value
|
|
140
|
+
*/
|
|
141
|
+
export function assertHeaderContains(res, header, substring) {
|
|
142
|
+
const value = res.headers.get(header);
|
|
143
|
+
if (value === null || !value.includes(substring)) {
|
|
144
|
+
throw new Error(`Expected header ${header} to contain "${substring}", got "${value}"`);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse JSON-LD from HTML (extracts from script tag)
|
|
151
|
+
*/
|
|
152
|
+
export function extractJsonLdFromHtml(html) {
|
|
153
|
+
const match = html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/);
|
|
154
|
+
if (!match) {
|
|
155
|
+
throw new Error('No JSON-LD found in HTML');
|
|
156
|
+
}
|
|
157
|
+
return JSON.parse(match[1]);
|
|
158
|
+
}
|
package/test/ldp.test.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LDP (Linked Data Platform) CRUD tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, before, after, beforeEach } 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('LDP CRUD Operations', () => {
|
|
18
|
+
let baseUrl;
|
|
19
|
+
|
|
20
|
+
before(async () => {
|
|
21
|
+
const result = await startTestServer();
|
|
22
|
+
baseUrl = result.baseUrl;
|
|
23
|
+
await createTestPod('ldptest');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
after(async () => {
|
|
27
|
+
await stopTestServer();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('GET', () => {
|
|
31
|
+
it('should return 404 for non-existent resource', async () => {
|
|
32
|
+
const res = await request('/ldptest/nonexistent.json');
|
|
33
|
+
assertStatus(res, 404);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return container listing for empty container', async () => {
|
|
37
|
+
const res = await request('/ldptest/public/');
|
|
38
|
+
|
|
39
|
+
assertStatus(res, 200);
|
|
40
|
+
assertHeaderContains(res, 'Link', 'Container');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return resource content', async () => {
|
|
44
|
+
// Create resource first (authenticated)
|
|
45
|
+
await request('/ldptest/public/test.json', {
|
|
46
|
+
method: 'PUT',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ hello: 'world' }),
|
|
49
|
+
auth: 'ldptest'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const res = await request('/ldptest/public/test.json');
|
|
53
|
+
|
|
54
|
+
assertStatus(res, 200);
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
assert.strictEqual(data.hello, 'world');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return ETag header', async () => {
|
|
60
|
+
await request('/ldptest/public/etag-test.txt', {
|
|
61
|
+
method: 'PUT',
|
|
62
|
+
body: 'test content',
|
|
63
|
+
auth: 'ldptest'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const res = await request('/ldptest/public/etag-test.txt');
|
|
67
|
+
|
|
68
|
+
assertStatus(res, 200);
|
|
69
|
+
assertHeader(res, 'ETag');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('HEAD', () => {
|
|
74
|
+
it('should return headers without body', async () => {
|
|
75
|
+
await request('/ldptest/public/head-test.txt', {
|
|
76
|
+
method: 'PUT',
|
|
77
|
+
body: 'test content',
|
|
78
|
+
auth: 'ldptest'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const res = await request('/ldptest/public/head-test.txt', {
|
|
82
|
+
method: 'HEAD'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assertStatus(res, 200);
|
|
86
|
+
assertHeader(res, 'Content-Type');
|
|
87
|
+
assertHeader(res, 'ETag');
|
|
88
|
+
|
|
89
|
+
const body = await res.text();
|
|
90
|
+
assert.strictEqual(body, '');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return 404 for non-existent', async () => {
|
|
94
|
+
const res = await request('/ldptest/public/no-such-file.txt', {
|
|
95
|
+
method: 'HEAD'
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
assertStatus(res, 404);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('PUT', () => {
|
|
103
|
+
it('should create new resource', async () => {
|
|
104
|
+
const res = await request('/ldptest/public/new-resource.json', {
|
|
105
|
+
method: 'PUT',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ created: true }),
|
|
108
|
+
auth: 'ldptest'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assertStatus(res, 201);
|
|
112
|
+
assertHeader(res, 'Location');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should update existing resource', async () => {
|
|
116
|
+
// Create
|
|
117
|
+
await request('/ldptest/public/update-me.txt', {
|
|
118
|
+
method: 'PUT',
|
|
119
|
+
body: 'original',
|
|
120
|
+
auth: 'ldptest'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Update
|
|
124
|
+
const res = await request('/ldptest/public/update-me.txt', {
|
|
125
|
+
method: 'PUT',
|
|
126
|
+
body: 'updated',
|
|
127
|
+
auth: 'ldptest'
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
assertStatus(res, 204);
|
|
131
|
+
|
|
132
|
+
// Verify
|
|
133
|
+
const verify = await request('/ldptest/public/update-me.txt');
|
|
134
|
+
const content = await verify.text();
|
|
135
|
+
assert.strictEqual(content, 'updated');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should create parent containers', async () => {
|
|
139
|
+
const res = await request('/ldptest/public/nested/deep/file.txt', {
|
|
140
|
+
method: 'PUT',
|
|
141
|
+
body: 'nested content',
|
|
142
|
+
auth: 'ldptest'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
assertStatus(res, 201);
|
|
146
|
+
|
|
147
|
+
// Verify parent exists
|
|
148
|
+
const parent = await request('/ldptest/public/nested/deep/');
|
|
149
|
+
assertStatus(parent, 200);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should reject PUT to container path', async () => {
|
|
153
|
+
const res = await request('/ldptest/public/invalid/', {
|
|
154
|
+
method: 'PUT',
|
|
155
|
+
body: 'cannot put to container',
|
|
156
|
+
auth: 'ldptest'
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
assertStatus(res, 409);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('POST', () => {
|
|
164
|
+
it('should create resource in container', async () => {
|
|
165
|
+
const res = await request('/ldptest/public/', {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: {
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
'Slug': 'posted-resource'
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({ posted: true }),
|
|
172
|
+
auth: 'ldptest'
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
assertStatus(res, 201);
|
|
176
|
+
assertHeader(res, 'Location');
|
|
177
|
+
|
|
178
|
+
// Verify created
|
|
179
|
+
const location = res.headers.get('Location');
|
|
180
|
+
const verify = await request(location);
|
|
181
|
+
assertStatus(verify, 200);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should use Slug header for filename', async () => {
|
|
185
|
+
const res = await request('/ldptest/public/', {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'text/plain',
|
|
189
|
+
'Slug': 'my-custom-name.txt'
|
|
190
|
+
},
|
|
191
|
+
body: 'slug test',
|
|
192
|
+
auth: 'ldptest'
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const location = res.headers.get('Location');
|
|
196
|
+
assert.ok(location.includes('my-custom-name'), 'Should use slug in filename');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should create container with Link header', async () => {
|
|
200
|
+
const res = await request('/ldptest/public/', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
'Slug': 'new-container',
|
|
204
|
+
'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
|
|
205
|
+
},
|
|
206
|
+
auth: 'ldptest'
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
assertStatus(res, 201);
|
|
210
|
+
|
|
211
|
+
const location = res.headers.get('Location');
|
|
212
|
+
assert.ok(location.endsWith('/'), 'Container location should end with /');
|
|
213
|
+
|
|
214
|
+
// Verify it's a container
|
|
215
|
+
const verify = await request(location);
|
|
216
|
+
assertHeaderContains(verify, 'Link', 'Container');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should reject POST to non-container', async () => {
|
|
220
|
+
await request('/ldptest/public/file-not-container.txt', {
|
|
221
|
+
method: 'PUT',
|
|
222
|
+
body: 'just a file',
|
|
223
|
+
auth: 'ldptest'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const res = await request('/ldptest/public/file-not-container.txt', {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
body: 'trying to post',
|
|
229
|
+
auth: 'ldptest'
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
assertStatus(res, 405);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('DELETE', () => {
|
|
237
|
+
it('should delete resource', async () => {
|
|
238
|
+
await request('/ldptest/public/to-delete.txt', {
|
|
239
|
+
method: 'PUT',
|
|
240
|
+
body: 'delete me',
|
|
241
|
+
auth: 'ldptest'
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const res = await request('/ldptest/public/to-delete.txt', {
|
|
245
|
+
method: 'DELETE',
|
|
246
|
+
auth: 'ldptest'
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
assertStatus(res, 204);
|
|
250
|
+
|
|
251
|
+
// Verify deleted
|
|
252
|
+
const verify = await request('/ldptest/public/to-delete.txt');
|
|
253
|
+
assertStatus(verify, 404);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return 404 for non-existent', async () => {
|
|
257
|
+
const res = await request('/ldptest/public/never-existed.txt', {
|
|
258
|
+
method: 'DELETE',
|
|
259
|
+
auth: 'ldptest'
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
assertStatus(res, 404);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should delete container', async () => {
|
|
266
|
+
// Create container
|
|
267
|
+
await request('/ldptest/public/', {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers: {
|
|
270
|
+
'Slug': 'container-to-delete',
|
|
271
|
+
'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
|
|
272
|
+
},
|
|
273
|
+
auth: 'ldptest'
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const res = await request('/ldptest/public/container-to-delete/', {
|
|
277
|
+
method: 'DELETE',
|
|
278
|
+
auth: 'ldptest'
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
assertStatus(res, 204);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('OPTIONS', () => {
|
|
286
|
+
it('should return allowed methods', async () => {
|
|
287
|
+
const res = await request('/ldptest/public/', {
|
|
288
|
+
method: 'OPTIONS'
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
assertStatus(res, 204);
|
|
292
|
+
const allow = assertHeader(res, 'Allow');
|
|
293
|
+
assert.ok(allow.includes('GET'), 'Should allow GET');
|
|
294
|
+
assert.ok(allow.includes('POST'), 'Should allow POST');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should return CORS headers', async () => {
|
|
298
|
+
const res = await request('/ldptest/public/', {
|
|
299
|
+
method: 'OPTIONS',
|
|
300
|
+
headers: { 'Origin': 'https://app.example.com' }
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
assertHeader(res, 'Access-Control-Allow-Origin');
|
|
304
|
+
assertHeader(res, 'Access-Control-Allow-Methods');
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('LDP Headers', () => {
|
|
309
|
+
it('should return Link type header for resource', async () => {
|
|
310
|
+
await request('/ldptest/public/resource-link.txt', {
|
|
311
|
+
method: 'PUT',
|
|
312
|
+
body: 'test',
|
|
313
|
+
auth: 'ldptest'
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const res = await request('/ldptest/public/resource-link.txt');
|
|
317
|
+
|
|
318
|
+
assertHeaderContains(res, 'Link', 'ldp#Resource');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should return Link type headers for container', async () => {
|
|
322
|
+
const res = await request('/ldptest/public/');
|
|
323
|
+
|
|
324
|
+
const link = res.headers.get('Link');
|
|
325
|
+
assert.ok(link.includes('ldp#Resource'), 'Should be LDP Resource');
|
|
326
|
+
assert.ok(link.includes('ldp#Container'), 'Should be LDP Container');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should return WAC-Allow header', async () => {
|
|
330
|
+
const res = await request('/ldptest/public/');
|
|
331
|
+
|
|
332
|
+
assertHeader(res, 'WAC-Allow');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should return Accept-Post for containers', async () => {
|
|
336
|
+
const res = await request('/ldptest/public/');
|
|
337
|
+
|
|
338
|
+
assertHeader(res, 'Accept-Post');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should return acl Link header for resource', async () => {
|
|
342
|
+
await request('/ldptest/public/acl-test.txt', {
|
|
343
|
+
method: 'PUT',
|
|
344
|
+
body: 'test',
|
|
345
|
+
auth: 'ldptest'
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const res = await request('/ldptest/public/acl-test.txt');
|
|
349
|
+
const link = res.headers.get('Link');
|
|
350
|
+
|
|
351
|
+
assert.ok(link.includes('rel="acl"'), 'Should have acl link relation');
|
|
352
|
+
assert.ok(link.includes('acl-test.txt.acl'), 'ACL should be resource.acl');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should return acl Link header for container', async () => {
|
|
356
|
+
const res = await request('/ldptest/public/');
|
|
357
|
+
const link = res.headers.get('Link');
|
|
358
|
+
|
|
359
|
+
assert.ok(link.includes('rel="acl"'), 'Should have acl link relation');
|
|
360
|
+
assert.ok(link.includes('.acl'), 'Should link to .acl');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|