diff --git a/__tests__/boot.ts b/__tests__/boot.ts index 991760a017..e9b89f2343 100644 --- a/__tests__/boot.ts +++ b/__tests__/boot.ts @@ -36,6 +36,7 @@ import { UserMarketingCta, UserNotification, } from '../src/entity'; +import { DatasetLocation } from '../src/entity/dataset/DatasetLocation'; import { OrganizationMemberRole, SourceMemberRoles, @@ -142,6 +143,7 @@ const LOGGED_IN_BODY = { youtube: null, linkedin: null, mastodon: null, + readme: null, language: undefined, isPlus: false, defaultFeedId: null, @@ -155,6 +157,7 @@ const LOGGED_IN_BODY = { coresRole: CoresRole.None, clickbaitTries: null, hasLocationSet: false, + location: null, }, marketingCta: null, feeds: [], @@ -413,6 +416,48 @@ describe('logged in boot', () => { expect(res.body.user.hasLocationSet).toBe(true); }); + it('should return location when user has locationId set', async () => { + const location = await con.getRepository(DatasetLocation).save({ + country: 'United States', + city: 'San Francisco', + subdivision: 'California', + iso2: 'US', + iso3: 'USA', + timezone: 'America/Los_Angeles', + ranking: 1, + }); + + await con.getRepository(User).save({ + ...usersFixture[0], + locationId: location.id, + }); + + mockLoggedIn(); + const res = await request(app.server) + .get(BASE_PATH) + .set('User-Agent', TEST_UA) + .set('Cookie', 'ory_kratos_session=value;') + .expect(200); + + expect(res.body.user.location).toEqual({ + id: location.id, + city: 'San Francisco', + subdivision: 'California', + country: 'United States', + }); + }); + + it('should return null location when user has no locationId', async () => { + mockLoggedIn(); + const res = await request(app.server) + .get(BASE_PATH) + .set('User-Agent', TEST_UA) + .set('Cookie', 'ory_kratos_session=value;') + .expect(200); + + expect(res.body.user.location).toBeNull(); + }); + it('should set kratos cookie expiration', async () => { mockLoggedIn(); const kratosCookie = 'ory_kratos_session'; @@ -1683,6 +1728,8 @@ describe('funnel boot', () => { 'subscriptionFlags', 'clickbaitTries', 'hasLocationSet', + 'location', + 'readme', ]), }); }); diff --git a/__tests__/updateUserInfo.ts b/__tests__/updateUserInfo.ts new file mode 100644 index 0000000000..8cf9b8aa5c --- /dev/null +++ b/__tests__/updateUserInfo.ts @@ -0,0 +1,664 @@ +import nock from 'nock'; +import { DataSource } from 'typeorm'; +import createOrGetConnection from '../src/db'; +import { + GraphQLTestClient, + GraphQLTestingState, + initializeGraphQLTesting, + disposeGraphQLTesting, + MockContext, +} from './helpers'; +import { User } from '../src/entity'; +import { clearFile, UploadPreset } from '../src/common/cloudinary'; + +let con: DataSource; +let state: GraphQLTestingState; +let client: GraphQLTestClient; +let loggedUser: string; + +// Mock cloudinary functions +jest.mock('../src/common/cloudinary', () => ({ + ...(jest.requireActual('../src/common/cloudinary') as Record< + string, + unknown + >), + clearFile: jest.fn(), + uploadAvatar: jest + .fn() + .mockResolvedValue({ url: 'https://cloudinary.com/avatar.jpg' }), + uploadProfileCover: jest + .fn() + .mockResolvedValue({ url: 'https://cloudinary.com/cover.jpg' }), +})); + +beforeAll(async () => { + con = await createOrGetConnection(); + state = await initializeGraphQLTesting( + () => new MockContext(con, loggedUser), + ); + client = state.client; +}); + +beforeEach(async () => { + loggedUser = '1'; + nock.cleanAll(); + jest.clearAllMocks(); + + // Save test users + await con.getRepository(User).save([ + { + id: '1', + name: 'Test User', + username: 'testuser', + image: 'https://daily.dev/test.jpg', + createdAt: new Date(), + }, + { + id: '2', + name: 'Another User', + username: 'anotheruser', + image: 'https://daily.dev/another.jpg', + createdAt: new Date(), + }, + ]); +}); + +afterAll(() => disposeGraphQLTesting(state)); + +describe('mutation updateUserInfo', () => { + const MUTATION = /* GraphQL */ ` + mutation updateUserInfo( + $data: UpdateUserInfoInput! + $upload: Upload + $coverUpload: Upload + ) { + updateUserInfo(data: $data, upload: $upload, coverUpload: $coverUpload) { + id + name + image + cover + username + permalink + bio + twitter + github + hashnode + createdAt + infoConfirmed + timezone + experienceLevel + language + readme + readmeHtml + location { + id + country + city + } + } + } + `; + + it('should not authorize when not logged in', async () => { + loggedUser = ''; + const res = await client.mutate(MUTATION, { + variables: { + data: { + name: 'Test User', + username: 'testuser', + }, + }, + }); + + expect(res.errors).toBeTruthy(); + expect(res.errors[0].extensions?.code).toEqual('UNAUTHENTICATED'); + }); + + it('should update user profile with basic fields', async () => { + loggedUser = '1'; + const repo = con.getRepository(User); + const user = await repo.findOneBy({ id: loggedUser }); + + const res = await client.mutate(MUTATION, { + variables: { + data: { + name: 'Updated Name', + username: 'newusername', + bio: 'New bio', + image: user?.image, + }, + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.updateUserInfo.name).toEqual('Updated Name'); + expect(res.data.updateUserInfo.username).toEqual('newusername'); + expect(res.data.updateUserInfo.bio).toEqual('New bio'); + + const updatedUser = await repo.findOneBy({ id: loggedUser }); + expect(updatedUser?.name).toEqual('Updated Name'); + expect(updatedUser?.username).toEqual('newusername'); + expect(updatedUser?.bio).toEqual('New bio'); + }); + + it('should update user profile with readme and generate readmeHtml', async () => { + loggedUser = '1'; + const repo = con.getRepository(User); + const user = await repo.findOneBy({ id: loggedUser }); + + const readme = + '# Hello World\n\nThis is my **readme** with [a link](https://example.com).'; + expect(user!.readme).toBeNull(); + expect(user!.readmeHtml).toBeNull(); + + const res = await client.mutate(MUTATION, { + variables: { + data: { + readme, + username: 'uuu1', + name: user!.name, + }, + }, + }); + + expect(res.errors).toBeFalsy(); + expect(res.data.updateUserInfo.readme).toEqual(readme); + expect(res.data.updateUserInfo.readmeHtml).toBeTruthy(); + + const updatedUser = await repo.findOneBy({ id: loggedUser }); + expect(updatedUser!.readme).toEqual(readme); + expect(updatedUser!.readmeHtml).toContain('