diff --git a/__tests__/schema/profile.ts b/__tests__/schema/profile.ts index 9e750643ca..16134dcd66 100644 --- a/__tests__/schema/profile.ts +++ b/__tests__/schema/profile.ts @@ -2375,4 +2375,146 @@ describe('mutation upsertUserGeneralExperience with repository', () => { 'ZOD_VALIDATION_ERROR', ); }); + + it('should create an opensource experience with custom repository (null id)', async () => { + loggedUser = '1'; + + const res = await client.mutate(UPSERT_OPENSOURCE_MUTATION, { + variables: { + input: { + type: 'opensource', + title: 'GitLab Contributor', + description: 'Contributing to a GitLab project', + startedAt: new Date('2023-01-01'), + repository: { + id: null, + owner: 'myorg', + name: 'myproject', + url: 'https://gitlab.com/myorg/myproject', + image: null, + }, + }, + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.upsertUserGeneralExperience).toMatchObject({ + id: expect.any(String), + type: 'opensource', + title: 'GitLab Contributor', + company: null, + customCompanyName: null, + repository: { + id: null, + owner: 'myorg', + name: 'myproject', + url: 'https://gitlab.com/myorg/myproject', + image: null, + }, + }); + + // Verify the repository is stored in flags + const saved = await con.getRepository(UserExperience).findOne({ + where: { id: res.data.upsertUserGeneralExperience.id }, + }); + expect(saved?.flags).toMatchObject({ + repository: { + id: null, + owner: 'myorg', + name: 'myproject', + url: 'https://gitlab.com/myorg/myproject', + image: null, + }, + }); + }); + + it('should create an opensource experience with custom repository without owner', async () => { + loggedUser = '1'; + + const res = await client.mutate(UPSERT_OPENSOURCE_MUTATION, { + variables: { + input: { + type: 'opensource', + title: 'Custom Repo Contributor', + startedAt: new Date('2023-01-01'), + repository: { + id: null, + owner: null, + name: 'standalone-project', + url: 'https://example.com/standalone-project', + image: null, + }, + }, + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.upsertUserGeneralExperience).toMatchObject({ + type: 'opensource', + repository: { + id: null, + owner: null, + name: 'standalone-project', + url: 'https://example.com/standalone-project', + image: null, + }, + }); + }); + + it('should update from GitHub repository to custom repository', async () => { + loggedUser = '1'; + + // First create with GitHub repository + const created = await client.mutate(UPSERT_OPENSOURCE_MUTATION, { + variables: { + input: { + type: 'opensource', + title: 'Initial Contribution', + startedAt: new Date('2023-01-01'), + repository: { + id: '10270250', + owner: 'facebook', + name: 'react', + url: 'https://github.com/facebook/react', + image: 'https://avatars.githubusercontent.com/u/69631?v=4', + }, + }, + }, + }); + + expect(created.errors).toBeFalsy(); + const experienceId = created.data.upsertUserGeneralExperience.id; + + // Update to custom repository + const updated = await client.mutate(UPSERT_OPENSOURCE_MUTATION, { + variables: { + id: experienceId, + input: { + type: 'opensource', + title: 'Moved to GitLab', + startedAt: new Date('2023-01-01'), + repository: { + id: null, + owner: 'myorg', + name: 'forked-project', + url: 'https://gitlab.com/myorg/forked-project', + image: null, + }, + }, + }, + }); + + expect(updated.errors).toBeFalsy(); + expect(updated.data.upsertUserGeneralExperience).toMatchObject({ + id: experienceId, + title: 'Moved to GitLab', + repository: { + id: null, + owner: 'myorg', + name: 'forked-project', + url: 'https://gitlab.com/myorg/forked-project', + image: null, + }, + }); + }); }); diff --git a/src/common/schema/profile.ts b/src/common/schema/profile.ts index cdcd1c5c49..6e5b746b80 100644 --- a/src/common/schema/profile.ts +++ b/src/common/schema/profile.ts @@ -51,11 +51,11 @@ export const userExperienceProjectSchema = z .extend(userExperienceInputBaseSchema.shape); export const repositoryInputSchema = z.object({ - id: z.string().min(1), - owner: z.string().min(1).max(100), + id: z.string().min(1).nullish(), + owner: z.string().max(100).nullish(), name: z.string().min(1).max(200), url: z.url(), - image: z.url(), + image: z.url().nullish(), }); export const userExperienceOpenSourceSchema = z diff --git a/src/entity/user/experiences/UserExperience.ts b/src/entity/user/experiences/UserExperience.ts index ba148a799e..f6957b8cef 100644 --- a/src/entity/user/experiences/UserExperience.ts +++ b/src/entity/user/experiences/UserExperience.ts @@ -23,10 +23,11 @@ export type UserExperienceFlags = Partial<{ customImage: string; removedEnrichment: boolean; repository: { - id: string; + id: string | null; + owner: string | null; name: string; url: string; - image: string; + image: string | null; }; }>; diff --git a/src/schema/profile.ts b/src/schema/profile.ts index 684380fe4d..8a6c529189 100644 --- a/src/schema/profile.ts +++ b/src/schema/profile.ts @@ -68,11 +68,11 @@ export const typeDefs = /* GraphQL */ ` } type UserExperienceRepository { - id: ID! + id: ID owner: String name: String! url: String! - image: String! + image: String } type UserExperience { @@ -134,11 +134,11 @@ export const typeDefs = /* GraphQL */ ` } input RepositoryInput { - id: ID! - owner: String! + id: ID + owner: String name: String! url: String! - image: String! + image: String } input UserGeneralExperienceInput { @@ -236,11 +236,11 @@ const generateExperienceToSave = async < parsed as R & { customDomain?: string | null; repository?: { - id: string; - owner: string; + id: string | null; + owner: string | null; name: string; url: string; - image: string; + image: string | null; } | null; };