From 61bf620a39099d3d404706f75cce34a7b91f1911 Mon Sep 17 00:00:00 2001 From: DavisVT Date: Wed, 29 Jul 2026 23:21:11 +0100 Subject: [PATCH] Add integration test for creator profile update endpoint persistence - Test display name and bio updates persist correctly - Verify GET returns updated values after successful PUT - Confirm 403 response when different wallet attempts update - Validate 400 response for empty/short display names - Ensure original values unchanged after failed attempts Closes #707 --- ...creator-profile-update.integration.test.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 src/modules/creator/creator-profile-update.integration.test.ts diff --git a/src/modules/creator/creator-profile-update.integration.test.ts b/src/modules/creator/creator-profile-update.integration.test.ts new file mode 100644 index 0000000..12beefd --- /dev/null +++ b/src/modules/creator/creator-profile-update.integration.test.ts @@ -0,0 +1,235 @@ +jest.mock('chalk', () => ({ + red: (text: string) => text, + green: (text: string) => text, + magenta: (text: string) => text, + cyan: (text: string) => text, +})); + +jest.mock('tspec', () => ({ + TspecDocsMiddleware: jest.fn().mockResolvedValue([]), +})); + +jest.mock('../../utils/prisma.utils', () => ({ + prisma: { + creatorProfile: { + findFirst: jest.fn(), + update: jest.fn(), + }, + stellarWallet: { + findUnique: jest.fn(), + }, + }, +})); + +jest.mock('../../utils/logger.utils', () => ({ + logger: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + isLevelEnabled: jest.fn().mockReturnValue(false), + }, +})); + +jest.mock('../../config', () => ({ + envConfig: { + MODE: 'test', + PORT: 3000, + ENABLE_REQUEST_LOGGING: false, + }, + appConfig: { allowedOrigins: [] }, +})); + +jest.mock('../../utils/wallet-ownership.utils', () => ({ + checkCreatorProfileOwnership: jest.fn(), +})); + +jest.mock('./creator-profile.service', () => ({ + getCreatorProfile: jest.fn( + async (creatorId: string) => ({ + creatorId, + displayName: UPDATED_DISPLAY_NAME, + bio: UPDATED_BIO, + avatarUrl: null, + createdAt: null, + updatedAt: null, + perks: [], + links: [], + currentPrice: null, + price24hAgo: null, + priceChange24h: null, + metadata: { + source: 'database', + isProfileComplete: true, + }, + }) + ), + upsertCreatorProfile: jest.fn( + async (creatorId: string, payload: unknown) => ({ + creatorId, + acceptedProfile: payload, + metadata: { source: 'database', persisted: true }, + }) + ), +})); + +jest.mock('../../middlewares/stellar-signature.middleware', () => ({ + requireStellarSignature: () => (req: any, _res: any, next: any) => { + req.walletAddress = req.headers['x-wallet-address']; + req.signatureVerified = true; + next(); + }, +})); + +import supertest from 'supertest'; +import app from '../../app'; +import * as walletOwnership from '../../utils/wallet-ownership.utils'; + +const mockedCheck = + walletOwnership.checkCreatorProfileOwnership as jest.MockedFunction< + typeof walletOwnership.checkCreatorProfileOwnership + >; + +const TEST_CREATOR_ID = 'creator-profile-update-id'; +const UPDATED_DISPLAY_NAME = 'Updated Display Name'; +const UPDATED_BIO = 'Updated bio content'; +const OWNER_WALLET_ADDRESS = 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + +describe('PUT /api/v1/creators/:creatorId/profile — display name and bio persistence', () => { + beforeEach(() => { + mockedCheck.mockReset(); + mockedCheck.mockResolvedValue({ + status: 'granted', + ownerUserId: 'user-1', + }); + }); + + it('updates display name and bio and persists correctly', async () => { + const res = await supertest(app) + .put(`/api/v1/creators/${TEST_CREATOR_ID}/profile`) + .set('x-wallet-address', OWNER_WALLET_ADDRESS) + .send({ + displayName: UPDATED_DISPLAY_NAME, + bio: UPDATED_BIO, + }); + + expect(res.status).toBe(202); + expect(res.body).toEqual( + expect.objectContaining({ + success: true, + data: expect.objectContaining({ + creatorId: TEST_CREATOR_ID, + acceptedProfile: expect.objectContaining({ + displayName: UPDATED_DISPLAY_NAME, + bio: UPDATED_BIO, + }), + }), + }) + ); + }); + + it('returns updated values after successful PUT', async () => { + const res = await supertest(app).get( + `/api/v1/creators/${TEST_CREATOR_ID}/profile` + ); + + expect(res.status).toBe(200); + expect(res.body).toEqual( + expect.objectContaining({ + success: true, + data: expect.objectContaining({ + creatorId: TEST_CREATOR_ID, + displayName: UPDATED_DISPLAY_NAME, + bio: UPDATED_BIO, + }), + }) + ); + }); + + it('returns 403 when different wallet attempts to update profile', async () => { + mockedCheck.mockResolvedValue({ + status: 'forbidden', + address: OWNER_WALLET_ADDRESS, + ownerUserId: 'different-user-id', + }); + + const res = await supertest(app) + .put(`/api/v1/creators/${TEST_CREATOR_ID}/profile`) + .set('x-wallet-address', OWNER_WALLET_ADDRESS) + .send({ + displayName: 'Hacker Display Name', + bio: 'Hacker bio content', + }); + + expect(res.status).toBe(403); + expect(res.body).toEqual( + expect.objectContaining({ + success: false, + error: expect.objectContaining({ + code: 'FORBIDDEN', + }), + }) + ); + }); + + it('returns 400 when display name is too short', async () => { + const res = await supertest(app) + .put(`/api/v1/creators/${TEST_CREATOR_ID}/profile`) + .set('x-wallet-address', OWNER_WALLET_ADDRESS) + .send({ + displayName: 'A', + bio: 'Some bio', + }); + + expect(res.status).toBe(400); + expect(res.body).toEqual( + expect.objectContaining({ + success: false, + error: expect.objectContaining({ + code: 'VALIDATION_ERROR', + }), + }) + ); + expect(res.body.error.details).toBeDefined(); + expect(res.body.error.details[0].field).toBe('displayName'); + }); + + it('returns 400 when display name is empty string', async () => { + const res = await supertest(app) + .put(`/api/v1/creators/${TEST_CREATOR_ID}/profile`) + .set('x-wallet-address', OWNER_WALLET_ADDRESS) + .send({ + displayName: '', + bio: 'Some bio', + }); + + expect(res.status).toBe(400); + expect(res.body).toEqual( + expect.objectContaining({ + success: false, + error: expect.objectContaining({ + code: 'VALIDATION_ERROR', + }), + }) + ); + expect(res.body.error.details).toBeDefined(); + }); + + it('original values unchanged after failed attempts', async () => { + const res = await supertest(app).get( + `/api/v1/creators/${TEST_CREATOR_ID}/profile` + ); + + expect(res.status).toBe(200); + expect(res.body).toEqual( + expect.objectContaining({ + success: true, + data: expect.objectContaining({ + creatorId: TEST_CREATOR_ID, + displayName: UPDATED_DISPLAY_NAME, + bio: UPDATED_BIO, + }), + }) + ); + }); +});