Skip to content

Commit ad86cd5

Browse files
committed
first tests
1 parent 5ee7f6e commit ad86cd5

File tree

6 files changed

+474
-3
lines changed

6 files changed

+474
-3
lines changed
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
import { assertNonNullish } from 'testkit/utils';
2+
import { graphql } from '../../testkit/gql';
3+
import * as GraphQLSchema from '../../testkit/gql/graphql';
4+
import { execute } from '../../testkit/graphql';
5+
import { initSeed } from '../../testkit/seed';
6+
7+
const CreatePersonalAccessTokenMutation = graphql(`
8+
mutation CreatePersonalAccessTokenMutation($input: CreatePersonalAccessTokenInput!) {
9+
createPersonalAccessToken(input: $input) {
10+
ok {
11+
privateAccessKey
12+
createdPersonalAccessToken {
13+
id
14+
title
15+
description
16+
permissions
17+
createdAt
18+
}
19+
}
20+
error {
21+
message
22+
details {
23+
title
24+
description
25+
}
26+
}
27+
}
28+
}
29+
`);
30+
31+
const DeletePersonalAccessTokenMutation = graphql(`
32+
mutation DeletePersonalAccessTokenMutation($input: DeletePersonalAccessTokenInput!) {
33+
deletePersonalAccessToken(input: $input) {
34+
ok {
35+
deletedPersonalAccessTokenId
36+
}
37+
error {
38+
message
39+
}
40+
}
41+
}
42+
`);
43+
44+
const OrganizationProjectTargetQuery1 = graphql(`
45+
query OrganizationProjectTargetQuery1(
46+
$organizationSlug: String!
47+
$projectSlug: String!
48+
$targetSlug: String!
49+
) {
50+
organization: organizationBySlug(organizationSlug: $organizationSlug) {
51+
id
52+
slug
53+
project: projectBySlug(projectSlug: $projectSlug) {
54+
id
55+
slug
56+
targetBySlug(targetSlug: $targetSlug) {
57+
id
58+
slug
59+
}
60+
}
61+
}
62+
}
63+
`);
64+
65+
test.concurrent('create: success with supertokens session', async () => {
66+
const { createOrg, ownerToken } = await initSeed().createOwner();
67+
const org = await createOrg();
68+
69+
const result = await execute({
70+
document: CreatePersonalAccessTokenMutation,
71+
variables: {
72+
input: {
73+
organization: {
74+
byId: org.organization.id,
75+
},
76+
title: 'a access token',
77+
description: 'Some description',
78+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
79+
permissions: [],
80+
},
81+
},
82+
authToken: ownerToken,
83+
}).then(e => e.expectNoGraphQLErrors());
84+
expect(result.createPersonalAccessToken.error).toEqual(null);
85+
expect(result.createPersonalAccessToken.ok).toEqual({
86+
privateAccessKey: expect.any(String),
87+
createdPersonalAccessToken: {
88+
id: expect.any(String),
89+
title: 'a access token',
90+
description: 'Some description',
91+
permissions: [],
92+
createdAt: expect.any(String),
93+
},
94+
});
95+
});
96+
97+
test.concurrent('create: failure invalid title', async ({ expect }) => {
98+
const { createOrg, ownerToken } = await initSeed().createOwner();
99+
const org = await createOrg();
100+
101+
const result = await execute({
102+
document: CreatePersonalAccessTokenMutation,
103+
variables: {
104+
input: {
105+
organization: {
106+
byId: org.organization.id,
107+
},
108+
title: ' ',
109+
description: 'Some description',
110+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
111+
permissions: [],
112+
},
113+
},
114+
authToken: ownerToken,
115+
}).then(e => e.expectNoGraphQLErrors());
116+
expect(result.createPersonalAccessToken.ok).toEqual(null);
117+
expect(result.createPersonalAccessToken.error).toMatchInlineSnapshot(`
118+
{
119+
details: {
120+
description: null,
121+
title: Can only contain letters, numbers, " ", "_", and "-".,
122+
},
123+
message: Invalid input provided.,
124+
}
125+
`);
126+
});
127+
128+
test.concurrent('create: failure invalid description', async ({ expect }) => {
129+
const { createOrg, ownerToken } = await initSeed().createOwner();
130+
const org = await createOrg();
131+
132+
const result = await execute({
133+
document: CreatePersonalAccessTokenMutation,
134+
variables: {
135+
input: {
136+
organization: {
137+
byId: org.organization.id,
138+
},
139+
title: 'a access token',
140+
description: new Array(300).fill('A').join(''),
141+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
142+
permissions: [],
143+
},
144+
},
145+
authToken: ownerToken,
146+
}).then(e => e.expectNoGraphQLErrors());
147+
expect(result.createPersonalAccessToken.ok).toEqual(null);
148+
expect(result.createPersonalAccessToken.error).toMatchInlineSnapshot(`
149+
{
150+
details: {
151+
description: Maximum length is 248 characters.,
152+
title: null,
153+
},
154+
message: Invalid input provided.,
155+
}
156+
`);
157+
});
158+
159+
test.concurrent('create: failure because no access to organization', async ({ expect }) => {
160+
const actor1 = await initSeed().createOwner();
161+
const actor2 = await initSeed().createOwner();
162+
const org = await actor1.createOrg();
163+
164+
const errors = await execute({
165+
document: CreatePersonalAccessTokenMutation,
166+
variables: {
167+
input: {
168+
organization: {
169+
byId: org.organization.id,
170+
},
171+
title: 'a access token',
172+
description: 'Some description',
173+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
174+
permissions: [],
175+
},
176+
},
177+
authToken: actor2.ownerToken,
178+
}).then(e => e.expectGraphQLErrors());
179+
expect(errors).toMatchObject([
180+
{
181+
extensions: {
182+
code: 'UNAUTHORISED',
183+
},
184+
185+
message: `No access (reason: "Missing permission for performing 'personalAccessToken:modify' on resource")`,
186+
path: ['createPersonalAccessToken'],
187+
},
188+
]);
189+
});
190+
191+
test.concurrent('delete: successfuly delete own access token', async ({ expect }) => {
192+
const { createOrg, ownerToken } = await initSeed().createOwner();
193+
const org = await createOrg();
194+
195+
const createResult = await execute({
196+
document: CreatePersonalAccessTokenMutation,
197+
variables: {
198+
input: {
199+
organization: {
200+
byId: org.organization.id,
201+
},
202+
title: 'a access token',
203+
description: 'Some description',
204+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
205+
permissions: [],
206+
},
207+
},
208+
authToken: ownerToken,
209+
}).then(e => e.expectNoGraphQLErrors());
210+
expect(createResult.createPersonalAccessToken.error).toEqual(null);
211+
assertNonNullish(createResult.createPersonalAccessToken.ok);
212+
213+
const deleteResult = await execute({
214+
document: DeletePersonalAccessTokenMutation,
215+
variables: {
216+
input: {
217+
personalAccessToken: {
218+
byId: createResult.createPersonalAccessToken.ok.createdPersonalAccessToken.id,
219+
},
220+
},
221+
},
222+
authToken: ownerToken,
223+
}).then(e => e.expectNoGraphQLErrors());
224+
expect(deleteResult.deletePersonalAccessToken.error).toEqual(null);
225+
expect(deleteResult.deletePersonalAccessToken.ok).toEqual({
226+
deletedPersonalAccessTokenId:
227+
createResult.createPersonalAccessToken.ok.createdPersonalAccessToken.id,
228+
});
229+
});
230+
231+
test.concurrent('delete: fail delete access token of another user', async ({ expect }) => {
232+
const { createOrg, ownerToken } = await initSeed().createOwner();
233+
const org = await createOrg();
234+
const user2 = await org.inviteAndJoinMember();
235+
// make user also an admin
236+
await user2.assignMemberRole({
237+
userId: user2.member.id,
238+
roleId: org.organization.owner.role.id,
239+
});
240+
241+
const createResult = await execute({
242+
document: CreatePersonalAccessTokenMutation,
243+
variables: {
244+
input: {
245+
organization: {
246+
byId: org.organization.id,
247+
},
248+
title: 'a access token',
249+
description: 'Some description',
250+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
251+
permissions: [],
252+
},
253+
},
254+
authToken: user2.memberToken,
255+
}).then(e => e.expectNoGraphQLErrors());
256+
expect(createResult.createPersonalAccessToken.error).toEqual(null);
257+
assertNonNullish(createResult.createPersonalAccessToken.ok);
258+
259+
const deleteResult = await execute({
260+
document: DeletePersonalAccessTokenMutation,
261+
variables: {
262+
input: {
263+
personalAccessToken: {
264+
byId: createResult.createPersonalAccessToken.ok.createdPersonalAccessToken.id,
265+
},
266+
},
267+
},
268+
authToken: ownerToken,
269+
}).then(e => e.expectGraphQLErrors());
270+
expect(deleteResult).toMatchObject([
271+
{
272+
extensions: {
273+
code: 'UNAUTHORISED',
274+
},
275+
message: `No access (reason: "Missing permission for performing 'accessToken:modify' on resource")`,
276+
path: ['deletePersonalAccessToken'],
277+
},
278+
]);
279+
});
280+
281+
test.concurrent('query GraphQL API on resources with access', async ({ expect }) => {
282+
const { createOrg, ownerToken } = await initSeed().createOwner();
283+
const org = await createOrg();
284+
const project = await org.createProject(GraphQLSchema.ProjectType.Federation);
285+
286+
const result = await execute({
287+
document: CreatePersonalAccessTokenMutation,
288+
variables: {
289+
input: {
290+
organization: {
291+
byId: org.organization.id,
292+
},
293+
title: 'a access token',
294+
description: 'a description',
295+
resources: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
296+
permissions: ['organization:describe', 'project:describe'],
297+
},
298+
},
299+
authToken: ownerToken,
300+
}).then(e => e.expectNoGraphQLErrors());
301+
expect(result.createPersonalAccessToken.error).toEqual(null);
302+
const organizationAccessToken = result.createPersonalAccessToken.ok!.privateAccessKey;
303+
304+
const projectQuery = await execute({
305+
document: OrganizationProjectTargetQuery1,
306+
variables: {
307+
organizationSlug: org.organization.slug,
308+
projectSlug: project.project.slug,
309+
targetSlug: project.target.slug,
310+
},
311+
authToken: organizationAccessToken,
312+
}).then(e => e.expectNoGraphQLErrors());
313+
expect(projectQuery).toEqual({
314+
organization: {
315+
id: expect.any(String),
316+
slug: org.organization.slug,
317+
project: {
318+
id: expect.any(String),
319+
slug: project.project.slug,
320+
targetBySlug: {
321+
id: expect.any(String),
322+
slug: project.target.slug,
323+
},
324+
},
325+
},
326+
});
327+
});
328+
329+
test.concurrent('query GraphQL API on resources without access', async ({ expect }) => {
330+
const { createOrg, ownerToken } = await initSeed().createOwner();
331+
const org = await createOrg();
332+
const project1 = await org.createProject(GraphQLSchema.ProjectType.Federation);
333+
const project2 = await org.createProject(GraphQLSchema.ProjectType.Federation);
334+
335+
const result = await execute({
336+
document: CreatePersonalAccessTokenMutation,
337+
variables: {
338+
input: {
339+
organization: {
340+
byId: org.organization.id,
341+
},
342+
title: 'a access token',
343+
description: 'a description',
344+
resources: {
345+
mode: GraphQLSchema.ResourceAssignmentModeType.Granular,
346+
projects: [
347+
{
348+
projectId: project1.project.id,
349+
targets: { mode: GraphQLSchema.ResourceAssignmentModeType.All },
350+
},
351+
],
352+
},
353+
permissions: ['organization:describe', 'project:describe'],
354+
},
355+
},
356+
authToken: ownerToken,
357+
}).then(e => e.expectNoGraphQLErrors());
358+
expect(result.createPersonalAccessToken.error).toEqual(null);
359+
assertNonNullish(result.createPersonalAccessToken.ok);
360+
const personalAccessToken = result.createPersonalAccessToken.ok.privateAccessKey;
361+
362+
const projectQuery = await execute({
363+
document: OrganizationProjectTargetQuery1,
364+
variables: {
365+
organizationSlug: org.organization.slug,
366+
projectSlug: project2.project.slug,
367+
targetSlug: project2.target.slug,
368+
},
369+
authToken: personalAccessToken,
370+
}).then(e => e.expectNoGraphQLErrors());
371+
expect(projectQuery).toEqual({
372+
organization: {
373+
id: expect.any(String),
374+
project: null,
375+
slug: org.organization.slug,
376+
},
377+
});
378+
});

packages/services/api/src/modules/organization/module.graphql.mappers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
import { OrganizationAccessToken } from './providers/organization-access-tokens';
77
import { OrganizationMemberRole } from './providers/organization-member-roles';
88
import { OrganizationMembership } from './providers/organization-members';
9+
import type { PersonalAccessToken } from './providers/personal-access-tokens';
910

1011
export type OrganizationConnectionMapper = readonly Organization[];
1112
export type OrganizationMapper = Organization;
@@ -14,3 +15,4 @@ export type OrganizationGetStartedMapper = OrganizationGetStarted;
1415
export type OrganizationInvitationMapper = OrganizationInvitation;
1516
export type MemberMapper = OrganizationMembership;
1617
export type OrganizationAccessTokenMapper = OrganizationAccessToken;
18+
export type PersonalAccessTokenMapper = PersonalAccessToken;

0 commit comments

Comments
 (0)