Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 110 additions & 10 deletions __tests__/schema/opportunity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5543,22 +5543,122 @@ describe('mutation parseOpportunity', () => {
expect(body.errors[0].message).toBe('File type not supported');
});

it('should not allow authenticated users to parse opportunity', async () => {
it('should parse opportunity for authenticated user', async () => {
loggedUser = '1';

const res = await client.mutate(MUTATION, {
variables: {
payload: {
url: 'https://example.com/opportunity',
trackingId = 'anon1';

fileTypeFromBuffer.mockResolvedValue({
ext: 'pdf',
mime: 'application/pdf',
});

const uploadResumeFromBufferSpy = jest.spyOn(
googleCloud,
'uploadResumeFromBuffer',
);

uploadResumeFromBufferSpy.mockResolvedValue(
`https://storage.cloud.google.com/${RESUME_BUCKET_NAME}/file`,
);

const deleteFileFromBucketSpy = jest.spyOn(
googleCloud,
'deleteFileFromBucket',
);

deleteFileFromBucketSpy.mockResolvedValue(true);

// Execute the mutation with a file upload
const res = await authorizeRequest(
request(app.server)
.post('/graphql')
.field(
'operations',
JSON.stringify({
query: MUTATION,
variables: {
payload: {
file: null,
},
},
}),
)
.field('map', JSON.stringify({ '0': ['variables.payload.file'] }))
.attach('0', './__tests__/fixture/screen.pdf'),
).expect(200);

const body = res.body;
expect(body.errors).toBeFalsy();

expect(body.data.parseOpportunity).toMatchObject({
title: 'Mocked Opportunity Title',
tldr: 'This is a mocked TL;DR of the opportunity.',
keywords: [
{ keyword: 'mock' },
{ keyword: 'opportunity' },
{ keyword: 'test' },
],
meta: {
employmentType: EmploymentType.FULL_TIME,
seniorityLevel: SeniorityLevel.SENIOR,
roleType: RoleType.Auto,
salary: {
min: 1000,
max: 2000,
period: SalaryPeriod.MONTHLY,
},
},
content: {
overview: {
content: 'This is the overview of the mocked opportunity.',
html: '<p>This is the overview of the mocked opportunity.</p>\n',
},
responsibilities: {
content: 'These are the responsibilities of the mocked opportunity.',
html: '<p>These are the responsibilities of the mocked opportunity.</p>\n',
},
requirements: {
content: 'These are the requirements of the mocked opportunity.',
html: '<p>These are the requirements of the mocked opportunity.</p>\n',
},
},
location: [
{
city: 'San Francisco',
country: 'USA',
subdivision: 'CA',
type: LocationType.REMOTE,
},
],
questions: [],
feedbackQuestions: [
{
title: 'Why did you reject this opportunity?',
placeholder: `E.g., Not interested in the tech stack, location doesn't work for me, compensation too low...`,
},
],
});

expect(res.errors).toBeDefined();
expect(res.errors?.[0].extensions.code).toBe('FORBIDDEN');
expect(res.errors?.[0].message).toBe(
'Not available for authenticated users yet',
);
const opportunity = await con.getRepository(OpportunityJob).findOne({
where: {
id: body.data.parseOpportunity.id,
},
});

expect(opportunity).toBeDefined();
expect(opportunity!.state).toBe(OpportunityState.DRAFT);

const opportunityRecruiter = await con
.getRepository(OpportunityUserRecruiter)
.findOne({
where: {
opportunityId: body.data.parseOpportunity.id,
userId: loggedUser,
},
});

expect(opportunityRecruiter).toBeDefined();
});
});

Expand Down
25 changes: 20 additions & 5 deletions src/schema/opportunity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2077,8 +2077,8 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
ctx: Context,
info,
): Promise<GQLOpportunity> => {
if (ctx.userId) {
throw new ForbiddenError('Not available for authenticated users yet');
if (!(ctx.userId || ctx.trackingId)) {
throw new ValidationError('User identifier is required');
}

const parseOpportunityPayload =
Expand Down Expand Up @@ -2175,16 +2175,20 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<

const opportunityResult = await ctx.con.transaction(
async (entityManager) => {
const flags: Opportunity['flags'] = {};

if (!ctx.userId) {
flags.anonUserId = ctx.trackingId; // save tracking id to attribute later
}

const opportunity = await entityManager
.getRepository(OpportunityJob)
.save(
entityManager.getRepository(OpportunityJob).create({
...parsedOpportunity,
state: OpportunityState.DRAFT,
content: opportunityContent,
flags: {
anonUserId: ctx.trackingId, // save tracking id to attribute later
},
flags,
} as DeepPartial<OpportunityJob>),
);

Expand All @@ -2200,6 +2204,17 @@ export const resolvers: IResolvers<unknown, BaseContext> = traceResolvers<
})),
);

if (ctx.userId) {
await entityManager
.getRepository(OpportunityUserRecruiter)
.insert(
entityManager.getRepository(OpportunityUserRecruiter).create({
opportunityId: opportunity.id,
userId: ctx.userId,
}),
);
}

return opportunity;
},
);
Expand Down
Loading