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
4 changes: 4 additions & 0 deletions .infra/crons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,8 @@ export const crons: Cron[] = [
name: 'clean-zombie-opportunities',
schedule: '30 6 * * *',
},
{
name: 'user-profile-updated-sync',
schedule: '45 */3 * * *',
},
];
239 changes: 239 additions & 0 deletions __tests__/cron/userProfileUpdatedSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { userProfileUpdatedSync as cron } from '../../src/cron/userProfileUpdatedSync';
import { expectSuccessfulCron, saveFixtures } from '../helpers';
import { DataSource } from 'typeorm';
import createOrGetConnection from '../../src/db';
import { crons } from '../../src/cron/index';
import { organizationsFixture } from '../fixture/opportunity';
import { userExperienceWorkFixture } from '../fixture/profile/work';
import { Organization } from '../../src/entity/Organization';
import { insertOrIgnoreUserExperienceSkills } from '../../src/entity/user/experiences/UserExperienceSkill';
import { User } from '../../src/entity/user/User';
import { usersFixture } from '../fixture/user';
import { DatasetLocation } from '../../src/entity/dataset/DatasetLocation';
import {
datasetLocationFixture,
userExperienceFixture,
} from '../fixture/profile/experience';
import { randomUUID } from 'node:crypto';
import { Company } from '../../src/entity/Company';
import { companyFixture } from '../fixture/company';
import { triggerTypedEvent } from '../../src/common';
import { UserProfileUpdatedMessage } from '@dailydotdev/schema';
import { UserExperience } from '../../src/entity/user/experiences/UserExperience';

jest.mock('../../src/common/typedPubsub', () => ({
...(jest.requireActual('../../src/common/typedPubsub') as Record<
string,
unknown
>),
triggerTypedEvent: jest.fn(),
}));

let con: DataSource;

beforeAll(async () => {
con = await createOrGetConnection();
});

beforeEach(async () => {
jest.clearAllMocks();

await saveFixtures(con, Organization, organizationsFixture);
await saveFixtures(con, User, usersFixture);
await saveFixtures(con, Company, companyFixture);

const datasetLocations = await con
.getRepository(DatasetLocation)
.save(datasetLocationFixture);

const experiencesToInsert = userExperienceFixture.map((item) => {
const experienceId = randomUUID();

return {
...item,
skills: undefined,
id: experienceId,
locationId: item.customLocation ? undefined : datasetLocations[0].id,
};
});

await con.getRepository(UserExperience).save(experiencesToInsert);

await Promise.all(
userExperienceWorkFixture.map((item, index) => {
const experienceId = experiencesToInsert[index].id;

return insertOrIgnoreUserExperienceSkills(
con,
experienceId,
userExperienceWorkFixture[index].skills || [],
);
}),
);
});

describe('userProfileUpdatedSync cron', () => {
it('should be registered', () => {
const registeredWorker = crons.find((item) => item.name === cron.name);

expect(registeredWorker).toBeDefined();
});

it('should publish updated user experiences', async () => {
await expectSuccessfulCron(cron);

expect(triggerTypedEvent).toHaveBeenCalledTimes(2);

const mockCalls = jest.mocked(triggerTypedEvent).mock.calls;

const user1Call = mockCalls.find(
(item) =>
(item[2] as unknown as UserProfileUpdatedMessage).profile?.userId ===
'1',
);
expect(user1Call).toBeDefined();

expect(user1Call![2]).toEqual({
profile: {
experiences: expect.arrayContaining([
{
companyName: 'daily.dev',
createdAt: expect.any(Number),
description: 'Working on API infrastructure',
employmentType: 0,
id: expect.any(String),
location: {
city: 'San Francisco',
country: 'USA',
subdivision: 'CA',
},
locationType: 3,
startedAt: expect.any(Number),
subtitle: 'Backend Team',
title: 'Senior Software Engineer',
type: 1,
updatedAt: expect.any(Number),
verified: true,
},
{
companyName: 'daily.dev',
createdAt: expect.any(Number),
description: 'Worked on search infrastructure',
employmentType: 3,
endedAt: expect.any(Number),

id: expect.any(String),
location: {
city: 'San Francisco',
country: 'United States',
subdivision: 'California',
},
locationType: 2,
startedAt: expect.any(Number),
title: 'Software Engineer',
type: 1,
updatedAt: expect.any(Number),
verified: false,
},
{
companyName: 'daily.dev',
createdAt: expect.any(Number),
description: 'Focused on distributed systems',
employmentType: 0,
endedAt: expect.any(Number),
id: expect.any(String),
location: {
city: 'San Francisco',
country: 'United States',
subdivision: 'California',
},
locationType: 0,
startedAt: expect.any(Number),
subtitle: 'Bachelor of Science',
title: 'Computer Science',
type: 2,
updatedAt: expect.any(Number),
verified: false,
grade: '9/5',
},
]),
skills: expect.arrayContaining([
{
experienceId: expect.any(String),
value: 'CMS',
},
{
experienceId: expect.any(String),
value: 'VIVO CMS',
},
{
experienceId: expect.any(String),
value: 'PHP',
},
{
experienceId: expect.any(String),
value: 'Paiting',
},
{
experienceId: expect.any(String),
value: 'Woodworking',
},
]),
userId: '1',
},
});

const user2Call = mockCalls.find(
(item) =>
(item[2] as unknown as UserProfileUpdatedMessage).profile?.userId ===
'2',
);
expect(user2Call).toBeDefined();

expect(user2Call![2]).toEqual({
profile: {
experiences: expect.arrayContaining([
{
companyName: 'daily.dev',
createdAt: expect.any(Number),
description: 'Managing product roadmap',
employmentType: 1,
id: expect.any(String),
location: {
city: 'San Francisco',
country: 'United States',
subdivision: 'California',
},
locationType: 0,
startedAt: expect.any(Number),
title: 'Product Manager',
type: 1,
updatedAt: expect.any(Number),
verified: true,
},
{
companyName: 'daily.dev',
createdAt: expect.any(Number),
description: 'Contributing to TypeScript projects',
employmentType: 0,
id: expect.any(String),
location: {
city: 'San Francisco',
country: 'United States',
subdivision: 'California',
},
locationType: 0,
startedAt: expect.any(Number),
title: 'Open Source Contributor',
type: 3,
updatedAt: expect.any(Number),
verified: false,
url: 'https://example.com/project',
},
]),
skills: expect.arrayContaining([]),
userId: '2',
},
});
});
});
11 changes: 11 additions & 0 deletions __tests__/fixture/company.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { DeepPartial } from 'typeorm';
import type { Company } from '../../src/entity/Company';

export const companyFixture: DeepPartial<Company>[] = [
{
id: 'dailydev',
name: 'daily.dev',
image: 'cloudinary.com/dailydev/121232121/image',
domains: ['daily.dev', 'dailydev.com'],
},
];
94 changes: 94 additions & 0 deletions __tests__/fixture/profile/experience.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { DeepPartial } from 'typeorm';
import type { UserExperience } from '../../../src/entity/user/experiences/UserExperience';
import { UserExperienceType } from '../../../src/entity/user/experiences/types';
import type { UserExperienceEducation } from '../../../src/entity/user/experiences/UserExperienceEducation';
import type { UserExperienceProject } from '../../../src/entity/user/experiences/UserExperienceProject';
import { EmploymentType, LocationType } from '@dailydotdev/schema';
import type { DatasetLocation } from '../../../src/entity/dataset/DatasetLocation';
import type { UserExperienceWork } from '../../../src/entity/user/experiences/UserExperienceWork';

export const datasetLocationFixture: DeepPartial<DatasetLocation>[] = [
{
country: 'United States',
city: 'San Francisco',
subdivision: 'California',
iso2: 'US',
iso3: 'USA',
},
];

export const userExperienceFixture: DeepPartial<
UserExperience &
UserExperienceEducation &
UserExperienceProject &
UserExperienceWork & {
skills?: string[];
}
>[] = [
{
userId: '1',
companyId: 'dailydev',
title: 'Senior Software Engineer',
subtitle: 'Backend Team',
description: 'Working on API infrastructure',
startedAt: new Date('2022-01-01'),
endedAt: null, // Current position
type: UserExperienceType.Work,
customLocation: {
city: 'San Francisco',
subdivision: 'CA',
country: 'USA',
},
locationType: LocationType.HYBRID,
skills: ['TypeScript', 'Node.js', 'PostgreSQL'],
verified: true,
},
{
userId: '1',
companyId: 'dailydev',
title: 'Software Engineer',
subtitle: null,
description: 'Worked on search infrastructure',
startedAt: new Date('2020-01-01'),
endedAt: new Date('2021-12-31'),
type: UserExperienceType.Work,
locationType: LocationType.OFFICE,
skills: ['Elasticsearch', 'Go', 'Docker'],
employmentType: EmploymentType.CONTRACT,
},
{
userId: '1',
companyId: 'dailydev',
title: 'Computer Science',
subtitle: 'Bachelor of Science',
description: 'Focused on distributed systems',
startedAt: new Date('2016-09-01'),
endedAt: new Date('2020-06-30'),
type: UserExperienceType.Education,
grade: '9/5',
},
{
userId: '2',
companyId: 'dailydev',
title: 'Open Source Contributor',
subtitle: null,
description: 'Contributing to TypeScript projects',
startedAt: new Date('2021-06-01'),
endedAt: null,
type: UserExperienceType.Project,
url: 'https://example.com/project',
},
{
userId: '2',
companyId: 'dailydev',
title: 'Product Manager',
subtitle: null,
description: 'Managing product roadmap',
startedAt: new Date('2021-01-01'),
endedAt: null,
type: UserExperienceType.Work,
employmentType: EmploymentType.FULL_TIME,
skills: ['Agile', 'Scrum', 'Roadmapping'],
verified: true,
},
];
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@connectrpc/connect-fastify": "^1.6.1",
"@connectrpc/connect-node": "^1.6.1",
"@dailydotdev/graphql-redis-subscriptions": "^2.4.3",
"@dailydotdev/schema": "0.2.51",
"@dailydotdev/schema": "0.2.53",
"@dailydotdev/ts-ioredis-pool": "^1.0.2",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.1.0",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading