@stamhoofd/backend-sgv-mock 2.122.0
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/LICENSE.md +32 -0
- package/package.json +37 -0
- package/src/boot.ts +73 -0
- package/src/endpoints/SGVMockEndpoint.test.ts +406 -0
- package/src/endpoints/SGVMockEndpoint.ts +596 -0
- package/src/index.ts +6 -0
- package/src/state/mock-state.ts +329 -0
- package/tests/vitest.setup.ts +11 -0
- package/tsconfig.build.json +18 -0
- package/tsconfig.json +12 -0
- package/tsconfig.test.json +16 -0
- package/vitest.config.js +12 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Free Use License for Non-Profit Organizations
|
|
2
|
+
|
|
3
|
+
Copyright 2020 Codawood BV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any non-profit organization with fewer than 2,000 members (each, a “Licensee”), to use this software and associated documentation files (the “Software”), subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
1. Eligibility:
|
|
8
|
+
This license applies only if neither the Licensee nor, if applicable, its parent or umbrella organization, is already subject to an existing license for this project. For the purposes of this license, a “parent or umbrella organization” means any organization with which the Licensee is affiliated, coordinated, or represented as part of a larger collective, including but not limited to national associations or federations.
|
|
9
|
+
|
|
10
|
+
2. Modifications:
|
|
11
|
+
Modifications to the Software are permitted only if they are published publicly under the MIT License, covering only the modifications; the original Software remains under this license and may not be sublicensed without a separate agreement. Any modification made by a Licensee (other than modifications by the original author, its representatives, or the copyright owner) shall be deemed to be distributed under the MIT License. Submission of modifications through publicly accessible channels, including but not limited to pull requests on code repositories such as GitHub, shall be considered an explicit acknowledgment and grant by the Licensee that such modifications are made available under the MIT License. Licensees may not sublicense, transfer, or assign the rights granted under this license except as expressly permitted herein.
|
|
12
|
+
|
|
13
|
+
3. Precedence of Existing Licenses:
|
|
14
|
+
If the Licensee, or its parent or umbrella organization, is already subject to an existing license for this project, that license shall take precedence, and no additional free license is granted. Nothing in this license shall be construed to override any pre-existing exclusive license agreement.
|
|
15
|
+
|
|
16
|
+
4. Data Protection / GDPR Responsibility:
|
|
17
|
+
Licensees are solely responsible for ensuring that any processing of personal data using this software complies with applicable data protection laws, including the EU General Data Protection Regulation (GDPR).
|
|
18
|
+
|
|
19
|
+
5. Termination and Revocation:
|
|
20
|
+
The copyright holders may revoke this license at any time, at their sole discretion.
|
|
21
|
+
|
|
22
|
+
6. Governing Law:
|
|
23
|
+
This license shall be governed by and construed in accordance with the laws of Belgium, without regard to conflict of law principles.
|
|
24
|
+
|
|
25
|
+
7. Third-Party Licenses:
|
|
26
|
+
Certain files or components included in the Software may be governed by separate licenses, including open-source licenses, or contributed by third parties under different terms. Such files are not automatically covered by this license. Licensees are responsible for reviewing and complying with any additional license terms that apply to these files.
|
|
27
|
+
|
|
28
|
+
For the purposes of this license, a “non-profit organization” means an entity recognized as non-profit under applicable law in its jurisdiction, which does not distribute profits to its members or owners.
|
|
29
|
+
|
|
30
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
31
|
+
|
|
32
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stamhoofd/backend-sgv-mock",
|
|
3
|
+
"version": "2.122.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"require": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc --build tsconfig.build.json",
|
|
13
|
+
"build:full": "yarn -s clear && yarn -s build",
|
|
14
|
+
"clear": "rm -rf ./dist && rm -f *.tsbuildinfo",
|
|
15
|
+
"dev": "wait-on ../../../shared/sgv/dist/index.js && concurrently -r 'yarn -s build --watch --preserveWatchOutput' \"wait-on ./dist/index.js && nodemon --quiet --inspect=5859 --watch dist --watch '../../../shared/sgv/dist/' --ext .ts,.json,.js --delay 1000ms --exec 'node --enable-source-maps ./dist/index.js' --signal SIGTERM\"",
|
|
16
|
+
"dev:build": "yarn -s build",
|
|
17
|
+
"dev:full": "yarn -s dev",
|
|
18
|
+
"lint": "eslint .",
|
|
19
|
+
"start": "yarn -s build && node --enable-source-maps ./dist/index.js",
|
|
20
|
+
"test": "vitest"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@simonbackx/simple-encoding": "2.26.10",
|
|
24
|
+
"@simonbackx/simple-endpoints": "1.21.1",
|
|
25
|
+
"@simonbackx/simple-errors": "*",
|
|
26
|
+
"@stamhoofd/sgv": "2.122.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "22.15.34",
|
|
30
|
+
"vitest": "4.1.8"
|
|
31
|
+
},
|
|
32
|
+
"license": "UNLICENCED",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"gitHead": "3c022b7d7801fb105acfaa401432a8d029abda61"
|
|
37
|
+
}
|
package/src/boot.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CORSPreflightEndpoint,
|
|
3
|
+
Request,
|
|
4
|
+
Router,
|
|
5
|
+
RouterServer,
|
|
6
|
+
} from "@simonbackx/simple-endpoints";
|
|
7
|
+
|
|
8
|
+
process.on("unhandledRejection", (error: Error) => {
|
|
9
|
+
console.error("unhandledRejection");
|
|
10
|
+
console.error(error.message, error.stack);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
process.env.TZ = "UTC";
|
|
15
|
+
|
|
16
|
+
if (new Date().getTimezoneOffset() !== 0) {
|
|
17
|
+
throw new Error("Process should always run in UTC timezone");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// SGV is an external API and callers do not send Stamhoofd's X-Version header.
|
|
21
|
+
Request.defaultVersion = 1;
|
|
22
|
+
|
|
23
|
+
/** Starts the standalone SGV mock server with permissive CORS so dashboard and Playwright runs can call it as an external API. */
|
|
24
|
+
const start = async () => {
|
|
25
|
+
const router = new Router();
|
|
26
|
+
await router.loadEndpoints(import.meta.dirname + "/endpoints");
|
|
27
|
+
router.endpoints.push(new CORSPreflightEndpoint());
|
|
28
|
+
|
|
29
|
+
const routerServer = new RouterServer(router);
|
|
30
|
+
routerServer.verbose = false;
|
|
31
|
+
routerServer.defaultHeaders = {
|
|
32
|
+
...routerServer.defaultHeaders,
|
|
33
|
+
"Access-Control-Allow-Origin": "*",
|
|
34
|
+
"Access-Control-Allow-Headers": "Authorization, Content-Type, Accept",
|
|
35
|
+
"Access-Control-Allow-Methods": "GET, POST, PATCH, OPTIONS",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const port = Number.parseInt(
|
|
39
|
+
process.env.PORT ?? process.env.STAMHOOFD_PORT ?? "9094",
|
|
40
|
+
10,
|
|
41
|
+
);
|
|
42
|
+
routerServer.listen(port);
|
|
43
|
+
console.log(`Started SGV mock on port ${port}.`);
|
|
44
|
+
|
|
45
|
+
const shutdown = async () => {
|
|
46
|
+
console.log("Shutting down SGV mock...");
|
|
47
|
+
routerServer.defaultHeaders = Object.assign(
|
|
48
|
+
routerServer.defaultHeaders,
|
|
49
|
+
{ Connection: "close" },
|
|
50
|
+
);
|
|
51
|
+
await routerServer.close();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
process.on("SIGTERM", () => {
|
|
56
|
+
shutdown().catch((error) => {
|
|
57
|
+
console.error(error);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
process.on("SIGINT", () => {
|
|
63
|
+
shutdown().catch((error) => {
|
|
64
|
+
console.error(error);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
start().catch((error) => {
|
|
71
|
+
console.error("unhandledRejection", error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { Request, TestServer } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { ObjectData } from '@simonbackx/simple-encoding';
|
|
3
|
+
import {
|
|
4
|
+
defaultSGVGroepNumber,
|
|
5
|
+
SGV_FUNCTION_PATH,
|
|
6
|
+
SGV_GROUP_PATH,
|
|
7
|
+
SGV_LOGIN_AUTHORIZE_PATH,
|
|
8
|
+
SGV_LOGIN_TOKEN_PATH,
|
|
9
|
+
SGV_MEMBER_LIST_FILTER_PATH,
|
|
10
|
+
SGV_MEMBER_LIST_PATH,
|
|
11
|
+
SGV_MEMBER_PATH,
|
|
12
|
+
SGV_PROFILE_PATH,
|
|
13
|
+
SGVProfielResponse,
|
|
14
|
+
SGV_SEARCH_SIMILAR_PATH,
|
|
15
|
+
sgvMemberPath,
|
|
16
|
+
} from '@stamhoofd/sgv';
|
|
17
|
+
import { sgvMockState } from '../state/mock-state.js';
|
|
18
|
+
import { SGVMockEndpoint } from './SGVMockEndpoint.js';
|
|
19
|
+
|
|
20
|
+
describe('Endpoint.SGVMock', () => {
|
|
21
|
+
const endpoint = new SGVMockEndpoint();
|
|
22
|
+
const testServer = new TestServer();
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
sgvMockState.reset();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('shows a debug page at the root path', async () => {
|
|
29
|
+
const response = await testServer.test(
|
|
30
|
+
endpoint,
|
|
31
|
+
Request.get({ path: '/' }),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(response.status).toBe(200);
|
|
35
|
+
expect(response.headers).toEqual(
|
|
36
|
+
expect.objectContaining({
|
|
37
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
expect(response.body).toContain('SGV mock data');
|
|
41
|
+
expect(response.body).toContain('Existing');
|
|
42
|
+
expect(response.body).toContain('member-1');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns OAuth tokens', async () => {
|
|
46
|
+
const response = await testServer.test(
|
|
47
|
+
endpoint,
|
|
48
|
+
Request.post({ path: SGV_LOGIN_TOKEN_PATH }),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(response.status).toBe(200);
|
|
52
|
+
expect(body(response)).toMatchObject({
|
|
53
|
+
access_token: 'sgv-mock-access-token',
|
|
54
|
+
refresh_token: 'sgv-mock-refresh-token',
|
|
55
|
+
expires_in: 3600,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('redirects OAuth authorize requests to the callback URL', async () => {
|
|
60
|
+
const response = await testServer.test(
|
|
61
|
+
endpoint,
|
|
62
|
+
Request.get({
|
|
63
|
+
path: SGV_LOGIN_AUTHORIZE_PATH,
|
|
64
|
+
query: {
|
|
65
|
+
client_id: 'client-id',
|
|
66
|
+
redirect_uri: 'https://example.com/oauth/sgv',
|
|
67
|
+
state: 'state-value',
|
|
68
|
+
response_type: 'code',
|
|
69
|
+
scope: 'openid',
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(response.status).toBe(302);
|
|
75
|
+
expect(response.headers.Location).toBe(
|
|
76
|
+
'https://example.com/oauth/sgv?code=sgv-mock&state=state-value',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('preserves callback query parameters and URL-encodes OAuth state', async () => {
|
|
81
|
+
const response = await testServer.test(
|
|
82
|
+
endpoint,
|
|
83
|
+
Request.get({
|
|
84
|
+
path: SGV_LOGIN_AUTHORIZE_PATH,
|
|
85
|
+
query: {
|
|
86
|
+
client_id: 'client-id',
|
|
87
|
+
redirect_uri:
|
|
88
|
+
'https://example.com/oauth/sgv?existing=value',
|
|
89
|
+
state: 'state value&with=symbols',
|
|
90
|
+
response_type: 'code',
|
|
91
|
+
scope: 'openid',
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(response.status).toBe(302);
|
|
97
|
+
expect(response.headers.Location).toBe(
|
|
98
|
+
'https://example.com/oauth/sgv?existing=value&code=sgv-mock&state=state+value%26with%3Dsymbols',
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('rejects OAuth authorize requests with missing parameters', async () => {
|
|
103
|
+
await expect(
|
|
104
|
+
testServer.test(
|
|
105
|
+
endpoint,
|
|
106
|
+
Request.get({ path: SGV_LOGIN_AUTHORIZE_PATH }),
|
|
107
|
+
),
|
|
108
|
+
).rejects.toThrow(/Missing OAuth parameter: client_id/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('accepts authorization-code and refresh-token exchange request bodies', async () => {
|
|
112
|
+
const authorizationCodeResponse = await testServer.test(
|
|
113
|
+
endpoint,
|
|
114
|
+
Request.post({
|
|
115
|
+
path: SGV_LOGIN_TOKEN_PATH,
|
|
116
|
+
body: {
|
|
117
|
+
client_id: 'client-id',
|
|
118
|
+
code: 'sgv-mock',
|
|
119
|
+
grant_type: 'authorization_code',
|
|
120
|
+
redirect_uri: 'https://example.com/oauth/sgv',
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
const refreshTokenResponse = await testServer.test(
|
|
125
|
+
endpoint,
|
|
126
|
+
Request.post({
|
|
127
|
+
path: SGV_LOGIN_TOKEN_PATH,
|
|
128
|
+
body: {
|
|
129
|
+
client_id: 'client-id',
|
|
130
|
+
refresh_token: 'sgv-mock-refresh-token',
|
|
131
|
+
grant_type: 'refresh_token',
|
|
132
|
+
redirect_uri: 'https://example.com/oauth/sgv',
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(body(authorizationCodeResponse)).toMatchObject({
|
|
138
|
+
access_token: 'sgv-mock-access-token',
|
|
139
|
+
});
|
|
140
|
+
expect(body(refreshTokenResponse)).toMatchObject({
|
|
141
|
+
refresh_token: 'sgv-mock-refresh-token',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns the configured group, functions and profile', async () => {
|
|
146
|
+
const groupResponse = await testServer.test(
|
|
147
|
+
endpoint,
|
|
148
|
+
Request.get({ path: SGV_GROUP_PATH }),
|
|
149
|
+
);
|
|
150
|
+
const functionResponse = await testServer.test(
|
|
151
|
+
endpoint,
|
|
152
|
+
Request.get({
|
|
153
|
+
path: SGV_FUNCTION_PATH,
|
|
154
|
+
query: { groep: defaultSGVGroepNumber },
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
const profileResponse = await testServer.test(
|
|
158
|
+
endpoint,
|
|
159
|
+
Request.get({ path: SGV_PROFILE_PATH }),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(body(groupResponse).groepen).toEqual(
|
|
163
|
+
expect.arrayContaining([
|
|
164
|
+
expect.objectContaining({
|
|
165
|
+
groepsnummer: defaultSGVGroepNumber,
|
|
166
|
+
naam: 'Prins Boudewijn Wetteren',
|
|
167
|
+
}),
|
|
168
|
+
expect.objectContaining({
|
|
169
|
+
groepsnummer: defaultSGVGroepNumber,
|
|
170
|
+
naam: 'Stamhoofd',
|
|
171
|
+
}),
|
|
172
|
+
]),
|
|
173
|
+
);
|
|
174
|
+
expect(
|
|
175
|
+
body(functionResponse).functies.map((f: any) => f.code),
|
|
176
|
+
).toContain('JIN');
|
|
177
|
+
expect(body(profileResponse)).toMatchObject({
|
|
178
|
+
functies: [{ groep: defaultSGVGroepNumber, code: 'VGA' }],
|
|
179
|
+
});
|
|
180
|
+
expect(profileResponse.body).not.toContain('_isAutoEncoder');
|
|
181
|
+
|
|
182
|
+
const profile = new ObjectData(body(profileResponse), { version: 0 }).decode(SGVProfielResponse);
|
|
183
|
+
expect(profile.functies[0].code).toBe('VGA');
|
|
184
|
+
expect(profile.functies[0].groep).toBe(defaultSGVGroepNumber);
|
|
185
|
+
expect(profile.functies[0].isActive).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('creates group functions', async () => {
|
|
189
|
+
const response = await testServer.test(
|
|
190
|
+
endpoint,
|
|
191
|
+
Request.post({
|
|
192
|
+
path: SGV_FUNCTION_PATH,
|
|
193
|
+
body: {
|
|
194
|
+
beschrijving: 'Beheerd in Stamhoofd',
|
|
195
|
+
groepen: [defaultSGVGroepNumber],
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(response.status).toBe(201);
|
|
201
|
+
expect(body(response)).toMatchObject({
|
|
202
|
+
beschrijving: 'Beheerd in Stamhoofd',
|
|
203
|
+
});
|
|
204
|
+
expect(sgvMockState.functions).toHaveLength(7);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('stores the current ledenlijst filter and returns paginated member summaries', async () => {
|
|
208
|
+
const filterResponse = await testServer.test(
|
|
209
|
+
endpoint,
|
|
210
|
+
Request.patch({
|
|
211
|
+
path: SGV_MEMBER_LIST_FILTER_PATH,
|
|
212
|
+
body: {
|
|
213
|
+
criteria: {
|
|
214
|
+
groepen: [defaultSGVGroepNumber],
|
|
215
|
+
oudleden: false,
|
|
216
|
+
},
|
|
217
|
+
type: 'lid',
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
expect(filterResponse.status).toBe(200);
|
|
222
|
+
expect(body(filterResponse)).toEqual({});
|
|
223
|
+
|
|
224
|
+
const response = await testServer.test(
|
|
225
|
+
endpoint,
|
|
226
|
+
Request.get({
|
|
227
|
+
path: SGV_MEMBER_LIST_PATH,
|
|
228
|
+
query: { aantal: 1, offset: 0 },
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(sgvMockState.currentFilter).toMatchObject({
|
|
233
|
+
criteria: { groepen: [defaultSGVGroepNumber] },
|
|
234
|
+
});
|
|
235
|
+
expect(body(response)).toMatchObject({
|
|
236
|
+
aantal: 1,
|
|
237
|
+
offset: 0,
|
|
238
|
+
totaal: 1,
|
|
239
|
+
leden: [{ id: 'member-1' }],
|
|
240
|
+
});
|
|
241
|
+
expect(
|
|
242
|
+
body(response).leden[0].waarden[
|
|
243
|
+
'be.vvksm.groepsadmin.model.column.GeboorteDatumColumn'
|
|
244
|
+
],
|
|
245
|
+
).toBe('01/02/2000');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('searches similar members', async () => {
|
|
249
|
+
const response = await testServer.test(
|
|
250
|
+
endpoint,
|
|
251
|
+
Request.get({
|
|
252
|
+
path: SGV_SEARCH_SIMILAR_PATH,
|
|
253
|
+
query: { voornaam: 'Existing', achternaam: 'Member' },
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(body(response)).toMatchObject({
|
|
258
|
+
leden: [
|
|
259
|
+
{
|
|
260
|
+
id: 'member-1',
|
|
261
|
+
voornaam: 'Existing',
|
|
262
|
+
achternaam: 'Member',
|
|
263
|
+
geboortedatum: '2000-02-01',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('fetches, creates and patches members', async () => {
|
|
270
|
+
const existing = await testServer.test(
|
|
271
|
+
endpoint,
|
|
272
|
+
Request.get({ path: sgvMemberPath('member-1') }),
|
|
273
|
+
);
|
|
274
|
+
expect(body(existing)).toMatchObject({
|
|
275
|
+
id: 'member-1',
|
|
276
|
+
vgagegevens: { voornaam: 'Existing' },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const created = await testServer.test(
|
|
280
|
+
endpoint,
|
|
281
|
+
Request.post({
|
|
282
|
+
path: `${SGV_MEMBER_PATH}?bevestig=true`,
|
|
283
|
+
body: {
|
|
284
|
+
vgagegevens: {
|
|
285
|
+
voornaam: 'New',
|
|
286
|
+
achternaam: 'Member',
|
|
287
|
+
geboortedatum: '2010-03-04',
|
|
288
|
+
},
|
|
289
|
+
adressen: [],
|
|
290
|
+
contacten: [],
|
|
291
|
+
functies: [],
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
|
+
expect(created.status).toBe(201);
|
|
296
|
+
expect(body(created)).toMatchObject({
|
|
297
|
+
id: 'member-2',
|
|
298
|
+
vgagegevens: { voornaam: 'New' },
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const patched = await testServer.test(
|
|
302
|
+
endpoint,
|
|
303
|
+
Request.patch({
|
|
304
|
+
path: `${sgvMemberPath('member-2')}?bevestig=true`,
|
|
305
|
+
body: { vgagegevens: { voornaam: 'Updated' } },
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
expect(body(patched)).toMatchObject({
|
|
309
|
+
id: 'member-2',
|
|
310
|
+
vgagegevens: { voornaam: 'Updated', achternaam: 'Member' },
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('returns created members in later list pages and the debug page', async () => {
|
|
315
|
+
await testServer.test(
|
|
316
|
+
endpoint,
|
|
317
|
+
Request.post({
|
|
318
|
+
path: `${SGV_MEMBER_PATH}?bevestig=true`,
|
|
319
|
+
body: {
|
|
320
|
+
vgagegevens: {
|
|
321
|
+
voornaam: 'Second',
|
|
322
|
+
achternaam: 'Member',
|
|
323
|
+
geboortedatum: '2011-03-04',
|
|
324
|
+
},
|
|
325
|
+
adressen: [],
|
|
326
|
+
contacten: [],
|
|
327
|
+
functies: [],
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
await testServer.test(
|
|
332
|
+
endpoint,
|
|
333
|
+
Request.post({
|
|
334
|
+
path: `${SGV_MEMBER_PATH}?bevestig=true`,
|
|
335
|
+
body: {
|
|
336
|
+
vgagegevens: {
|
|
337
|
+
voornaam: 'Third',
|
|
338
|
+
achternaam: 'Member',
|
|
339
|
+
geboortedatum: '2012-03-04',
|
|
340
|
+
},
|
|
341
|
+
adressen: [],
|
|
342
|
+
contacten: [],
|
|
343
|
+
functies: [],
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const page = await testServer.test(
|
|
349
|
+
endpoint,
|
|
350
|
+
Request.get({
|
|
351
|
+
path: SGV_MEMBER_LIST_PATH,
|
|
352
|
+
query: { aantal: 1, offset: 2 },
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
const debug = await testServer.test(
|
|
356
|
+
endpoint,
|
|
357
|
+
Request.get({ path: '/' }),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
expect(body(page)).toMatchObject({
|
|
361
|
+
aantal: 1,
|
|
362
|
+
offset: 2,
|
|
363
|
+
totaal: 3,
|
|
364
|
+
leden: [{ id: 'member-3' }],
|
|
365
|
+
});
|
|
366
|
+
expect(debug.body).toContain('Third');
|
|
367
|
+
expect(debug.body).toContain('member-3');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('can end member functions through patches', async () => {
|
|
371
|
+
const patched = await testServer.test(
|
|
372
|
+
endpoint,
|
|
373
|
+
Request.patch({
|
|
374
|
+
path: `${sgvMemberPath('member-1')}?bevestig=true`,
|
|
375
|
+
body: {
|
|
376
|
+
functies: [
|
|
377
|
+
{
|
|
378
|
+
functie: 'd5f75b320b812440010b812555c1039b',
|
|
379
|
+
groep: defaultSGVGroepNumber,
|
|
380
|
+
begin: '2020-01-01',
|
|
381
|
+
einde: '2026-01-01',
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
expect(body(patched).functies[0]).toMatchObject({
|
|
389
|
+
functie: 'd5f75b320b812440010b812555c1039b',
|
|
390
|
+
einde: '2026-01-01',
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('returns an error for unknown members', async () => {
|
|
395
|
+
await expect(
|
|
396
|
+
testServer.test(
|
|
397
|
+
endpoint,
|
|
398
|
+
Request.get({ path: sgvMemberPath('missing') }),
|
|
399
|
+
),
|
|
400
|
+
).rejects.toThrow(/SGV member not found/);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
function body(response: { body: any }): any {
|
|
405
|
+
return JSON.parse(response.body as string);
|
|
406
|
+
}
|