@ossy/workspaces 1.12.0 → 1.13.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/package.json +11 -2
- package/src/sync-workspace-membership.task.js +40 -0
- package/src/workspaces.spec.js +258 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/workspaces",
|
|
3
3
|
"description": "Workspaces feature package — create, select, and manage workspaces, users, and invitations",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.13.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"module": "./src/index.js",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
".": "./src/index.js",
|
|
10
10
|
"./workspace-invitation.email.jsx": "./src/workspace-invitation.email.jsx"
|
|
11
11
|
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --verbose"
|
|
14
|
+
},
|
|
12
15
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
13
16
|
"license": "MIT",
|
|
14
17
|
"ossy": {
|
|
@@ -20,6 +23,12 @@
|
|
|
20
23
|
"@ossy/sdk-react": ">=1.0.0",
|
|
21
24
|
"react": ">=19.0.0 <20.0.0"
|
|
22
25
|
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@jest/globals": "^30.2.0",
|
|
28
|
+
"@ossy/platform": "^1.35.0",
|
|
29
|
+
"casual": "^1.6.2",
|
|
30
|
+
"jest": "^30.2.0"
|
|
31
|
+
},
|
|
23
32
|
"publishConfig": {
|
|
24
33
|
"access": "public",
|
|
25
34
|
"registry": "https://registry.npmjs.org"
|
|
@@ -28,5 +37,5 @@
|
|
|
28
37
|
"/src",
|
|
29
38
|
"README.md"
|
|
30
39
|
],
|
|
31
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "24df2bde0d5d8794c5a82b9e802a2594ca73a5d9"
|
|
32
41
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { User, UsersEvents } from '@ossy/users'
|
|
3
|
+
|
|
4
|
+
export const metadata = {
|
|
5
|
+
id: 'sync-workspace-membership',
|
|
6
|
+
triggers: [
|
|
7
|
+
{ aggregateType: 'Workspace', event: 'Created' },
|
|
8
|
+
{ aggregateType: 'Workspace', event: 'UserInvitationAccepted' },
|
|
9
|
+
{ aggregateType: 'Workspace', event: 'UserRemoved' },
|
|
10
|
+
],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function run ({ event, sdk }) {
|
|
14
|
+
const workspaceId = event.aggregateId
|
|
15
|
+
|
|
16
|
+
if (event.type === 'UserRemoved') {
|
|
17
|
+
const userId = event.payload.userId
|
|
18
|
+
|
|
19
|
+
if (sdk) {
|
|
20
|
+
await sdk.users.removeWorkspace({ userId, workspaceId })
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await Aggregate.Of(User, userId)
|
|
25
|
+
.then(Aggregate.Add(UsersEvents.WorkspaceLeft({ workspaceId, createdBy: userId })))
|
|
26
|
+
.then(Aggregate.Save())
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const userId = event.createdBy
|
|
31
|
+
|
|
32
|
+
if (sdk) {
|
|
33
|
+
await sdk.users.addWorkspace({ userId, workspaceId, createdBy: userId })
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await Aggregate.Of(User, userId)
|
|
38
|
+
.then(Aggregate.Add(UsersEvents.WorkspaceJoined({ workspaceId, createdBy: userId })))
|
|
39
|
+
.then(Aggregate.Save())
|
|
40
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import casual from 'casual'
|
|
2
|
+
import { TestUtil } from '@ossy/platform/test'
|
|
3
|
+
|
|
4
|
+
function workspaceHeaders(userToken, workspaceId) {
|
|
5
|
+
return {
|
|
6
|
+
'Content-Type': 'application/json',
|
|
7
|
+
Authorization: userToken,
|
|
8
|
+
workspaceId,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('[/workspaces][POST]', () => {
|
|
13
|
+
|
|
14
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
15
|
+
endpoint: '/workspaces',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('given a workspace name', () => {
|
|
21
|
+
it('must return OK 200 together with the newly created workspace and create a WorkspaceCreatedEvent', async () => {
|
|
22
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
23
|
+
const workspaceName = casual.word
|
|
24
|
+
|
|
25
|
+
const response = await TestUtil.MakeRequest({
|
|
26
|
+
endpoint: '/workspaces',
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
29
|
+
body: JSON.stringify({ name: workspaceName }),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const body = await response.json()
|
|
33
|
+
|
|
34
|
+
const workspaceCreatedEvent = await TestUtil.GetEvent({
|
|
35
|
+
aggregateType: 'Workspace',
|
|
36
|
+
type: 'Created',
|
|
37
|
+
createdBy: user.id,
|
|
38
|
+
'payload.name': workspaceName
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
expect(!!workspaceCreatedEvent).toEqual(true)
|
|
42
|
+
expect(response.status).toEqual(200)
|
|
43
|
+
expect(response.headers.get('Content-Type').includes('application/json')).toEqual(true)
|
|
44
|
+
expect(body.id).toEqual(workspaceCreatedEvent.aggregateId)
|
|
45
|
+
expect(body.name).toEqual(workspaceName)
|
|
46
|
+
expect(body.createdBy).toEqual(user.id)
|
|
47
|
+
expect(body.created).toEqual(workspaceCreatedEvent.created)
|
|
48
|
+
expect(body.apiTokens).toEqual([])
|
|
49
|
+
expect(Array.isArray(body.resourceTemplates)).toBe(true)
|
|
50
|
+
expect(body.resourceTemplates.length).toEqual(0)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('[/workspaces][GET]', () => {
|
|
57
|
+
|
|
58
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
59
|
+
endpoint: '/workspaces',
|
|
60
|
+
method: 'GET',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('must return a minified list of workspaces the user have created', async () => {
|
|
65
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
66
|
+
|
|
67
|
+
const workspace = await TestUtil.MakeRequest({
|
|
68
|
+
endpoint: '/workspaces',
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
71
|
+
body: JSON.stringify({ name: casual.word }),
|
|
72
|
+
}).then(response => response.json())
|
|
73
|
+
|
|
74
|
+
const response = await TestUtil.MakeRequest({
|
|
75
|
+
endpoint: '/workspaces',
|
|
76
|
+
method: 'GET',
|
|
77
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const responseBody = await response.json()
|
|
81
|
+
|
|
82
|
+
expect(response.status).toEqual(200)
|
|
83
|
+
expect(responseBody).toEqual([{
|
|
84
|
+
name: workspace.name,
|
|
85
|
+
id: workspace.id
|
|
86
|
+
}])
|
|
87
|
+
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('[/workspaces/:workspaceId][GET]', () => {
|
|
93
|
+
|
|
94
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
95
|
+
endpoint: '/workspaces/123',
|
|
96
|
+
method: 'GET',
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('must return the requested workspace', async () => {
|
|
101
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
102
|
+
|
|
103
|
+
const workspace = await TestUtil.MakeRequest({
|
|
104
|
+
endpoint: '/workspaces',
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
107
|
+
body: JSON.stringify({ name: casual.word }),
|
|
108
|
+
}).then(response => response.json())
|
|
109
|
+
|
|
110
|
+
const response = await TestUtil.MakeRequest({
|
|
111
|
+
endpoint: `/workspaces/${workspace.id}`,
|
|
112
|
+
method: 'GET',
|
|
113
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const responseBody = await response.json()
|
|
117
|
+
|
|
118
|
+
expect(response.status).toEqual(200)
|
|
119
|
+
expect(responseBody).toEqual(workspace)
|
|
120
|
+
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('[/resource-templates][POST]', () => {
|
|
126
|
+
|
|
127
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
128
|
+
endpoint: '/resource-templates',
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/json', 'workspaceId': 'ws-test' },
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('must return the saved templates and create a ResourceTemplatesImported event', async () => {
|
|
134
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
135
|
+
|
|
136
|
+
const workspace = await TestUtil.MakeRequest({
|
|
137
|
+
endpoint: '/workspaces',
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
140
|
+
body: JSON.stringify({ name: casual.word }),
|
|
141
|
+
}).then(response => response.json())
|
|
142
|
+
|
|
143
|
+
const imported = [
|
|
144
|
+
{
|
|
145
|
+
id: 'test/custom-foo',
|
|
146
|
+
name: 'Foo',
|
|
147
|
+
fields: [{ name: 'title', type: 'text' }],
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
const response = await TestUtil.MakeRequest({
|
|
152
|
+
endpoint: '/resource-templates',
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: workspaceHeaders(user.token, workspace.id),
|
|
155
|
+
body: JSON.stringify(imported)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const responseBody = await response.json()
|
|
159
|
+
|
|
160
|
+
const resourceTemplatesImportedEvent = await TestUtil.GetEvent({
|
|
161
|
+
aggregateType: 'Workspace',
|
|
162
|
+
type: 'ResourceTemplatesImported',
|
|
163
|
+
createdBy: user.id,
|
|
164
|
+
'payload.templates': imported
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
expect(response.status).toEqual(200)
|
|
168
|
+
expect(responseBody).toEqual(imported)
|
|
169
|
+
expect(!!resourceTemplatesImportedEvent).toEqual(true)
|
|
170
|
+
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('[/resource-templates][GET]', () => {
|
|
176
|
+
|
|
177
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
178
|
+
endpoint: '/resource-templates',
|
|
179
|
+
method: 'GET',
|
|
180
|
+
headers: { 'Content-Type': 'application/json', 'workspaceId': 'ws-test' },
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('must return the saved templates event', async () => {
|
|
184
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
185
|
+
|
|
186
|
+
const workspace = await TestUtil.MakeRequest({
|
|
187
|
+
endpoint: '/workspaces',
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
190
|
+
body: JSON.stringify({ name: casual.word }),
|
|
191
|
+
}).then(response => response.json())
|
|
192
|
+
|
|
193
|
+
const imported = [
|
|
194
|
+
{
|
|
195
|
+
id: 'test/custom-bar',
|
|
196
|
+
name: 'Bar',
|
|
197
|
+
fields: [{ name: 'title', type: 'text' }],
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
const response = await TestUtil.MakeRequest({
|
|
202
|
+
endpoint: '/resource-templates',
|
|
203
|
+
method: 'POST',
|
|
204
|
+
headers: workspaceHeaders(user.token, workspace.id),
|
|
205
|
+
body: JSON.stringify(imported)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const responseBody = await response.json()
|
|
209
|
+
|
|
210
|
+
expect(response.status).toEqual(200)
|
|
211
|
+
expect(responseBody).toEqual(imported)
|
|
212
|
+
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('[/tokens][POST]', () => {
|
|
218
|
+
|
|
219
|
+
TestUtil.AssertAuthenticationNeeded({
|
|
220
|
+
endpoint: '/tokens',
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: { 'Content-Type': 'application/json', 'workspaceId': 'ws-test' },
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('must return an api token and create an ApiTokenCreated event', async () => {
|
|
226
|
+
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
227
|
+
|
|
228
|
+
const workspace = await TestUtil.MakeRequest({
|
|
229
|
+
endpoint: '/workspaces',
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': user.token },
|
|
232
|
+
body: JSON.stringify({ name: casual.word }),
|
|
233
|
+
}).then(response => response.json())
|
|
234
|
+
|
|
235
|
+
const response = await TestUtil.MakeRequest({
|
|
236
|
+
endpoint: '/tokens',
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: workspaceHeaders(user.token, workspace.id),
|
|
239
|
+
body: JSON.stringify({ description: 'Api token for GitHub Actions' })
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const responseBody = await response.json()
|
|
243
|
+
const tokenPayload = JSON.parse(Buffer.from(responseBody.token.split('.')[1], 'base64').toString())
|
|
244
|
+
|
|
245
|
+
expect(response.status).toEqual(200)
|
|
246
|
+
expect(responseBody).toEqual(expect.objectContaining({
|
|
247
|
+
id: expect.any(String),
|
|
248
|
+
token: expect.any(String)
|
|
249
|
+
}))
|
|
250
|
+
expect(tokenPayload).toEqual(expect.objectContaining({
|
|
251
|
+
workspaceId: workspace.id,
|
|
252
|
+
exp: expect.any(Number),
|
|
253
|
+
iat: expect.any(Number)
|
|
254
|
+
}))
|
|
255
|
+
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
})
|