@ossy/workspaces 1.12.0 → 1.13.1

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 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.12.0",
4
+ "version": "1.13.1",
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.1",
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": "c6697078268867d88553ca0bac08faad5cea1546"
40
+ "gitHead": "a144d7767264d96bc6ae095784d4f884f03a89a0"
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
+ })