import { faker } from '@faker-js/faker'; import { uid } from 'uid'; import { UserRole } from '../../services/userManagementService/index.js'; import createContentItemTypes from '../../test/fixtureHelpers/createContentItemTypes.js'; import createOrg from '../../test/fixtureHelpers/createOrg.js'; import { makeTransactionalTestWithFixture } from '../../test/harness/transactionalTest.js'; import { CoopError } from '../../utils/errors.js'; import { kyselyUserInsert } from './userKyselyPersistence.js'; describe('OrgAPI', () => { const testWithFixture = makeTransactionalTestWithFixture(async ({ deps }) => { const { org } = await createOrg( { KyselyPg: deps.KyselyPg, ModerationConfigService: deps.ModerationConfigService, ApiKeyService: deps.ApiKeyService, }, uid(), ); return { org }; }); describe('getGraphQLOrgFromId', () => { testWithFixture( 'returns the org parent for an existing id', async ({ deps, org }) => { const result = await deps.OrgAPIDataSource.getGraphQLOrgFromId(org.id); expect(result).toMatchObject({ id: org.id, name: org.name, email: org.email, }); }, ); testWithFixture( 'throws when the org does not exist (replaces Sequelize rejectOnEmpty)', async ({ deps }) => { const missingId = `missing-${uid()}`; await expect( deps.OrgAPIDataSource.getGraphQLOrgFromId(missingId), ).rejects.toThrow(/Organization not found/); }, ); }); describe('updateOrgInfo', () => { testWithFixture('throws when the org does not exist', async ({ deps }) => { await expect( deps.OrgAPIDataSource.updateOrgInfo(`missing-${uid()}`, { name: 'whatever', }), ).rejects.toThrow(/Organization not found/); }); testWithFixture( 'returns the updated parent when the org exists', async ({ deps, org }) => { const newName = `Renamed_${uid()}`; const result = await deps.OrgAPIDataSource.updateOrgInfo(org.id, { name: newName, }); expect(result.id).toBe(org.id); expect(result.name).toBe(newName); }, ); testWithFixture( 'throws a BadRequest with a pointer for malformed email', async ({ deps, org }) => { await expect( deps.OrgAPIDataSource.updateOrgInfo(org.id, { email: 'not-an-email', }), ).rejects.toMatchObject({ name: 'BadRequestError', status: 400, pointer: '/input/email', }); }, ); testWithFixture( 'throws a BadRequest for malformed websiteUrl (javascript: scheme)', async ({ deps, org }) => { await expect( deps.OrgAPIDataSource.updateOrgInfo(org.id, { // eslint-disable-next-line no-script-url websiteUrl: 'javascript:alert(1)', }), ).rejects.toMatchObject({ name: 'BadRequestError', pointer: '/input/websiteUrl', }); }, ); testWithFixture( 'throws a BadRequest for empty name', async ({ deps, org }) => { await expect( deps.OrgAPIDataSource.updateOrgInfo(org.id, { name: '' }), ).rejects.toBeInstanceOf(CoopError); }, ); testWithFixture( 'does not touch the DB when validation fails (org not found only surfaces after validation passes)', async ({ deps }) => { // If validation ran AFTER the DB lookup we'd get "Organization not // found" here; ensure we see the BadRequest instead. await expect( deps.OrgAPIDataSource.updateOrgInfo(`missing-${uid()}`, { email: 'not-an-email', }), ).rejects.toMatchObject({ name: 'BadRequestError', pointer: '/input/email', }); }, ); }); describe('getContentTypesForOrg', () => { testWithFixture( 'returns every item type for the org with the fields read by the ContentType resolver', async ({ deps, org }) => { const { itemTypes } = await createContentItemTypes({ moderationConfigService: deps.ModerationConfigService, orgId: org.id, numItemTypes: 1, extra: {}, }); const result = await deps.OrgAPIDataSource.getContentTypesForOrg( org.id, ); const actualIds = new Set(result.map((it) => it.id)); for (const created of itemTypes) { expect(actualIds.has(created.id)).toBe(true); } for (const it of result) { expect(it.orgId).toBe(org.id); expect(typeof it.id).toBe('string'); expect(typeof it.name).toBe('string'); expect(['CONTENT', 'USER', 'THREAD']).toContain(it.kind); expect(Array.isArray(it.schema)).toBe(true); } }, ); testWithFixture( 'returns all item-type kinds, not just CONTENT (createOrg seeds a default USER)', async ({ deps, org }) => { const result = await deps.OrgAPIDataSource.getContentTypesForOrg( org.id, ); expect(result.length).toBeGreaterThan(0); expect(result.some((it) => it.kind === 'USER')).toBe(true); }, ); testWithFixture( 'does not leak item types across orgs', async ({ deps, org }) => { const { org: otherOrg } = await createOrg( { KyselyPg: deps.KyselyPg, ModerationConfigService: deps.ModerationConfigService, ApiKeyService: deps.ApiKeyService, }, uid(), ); const result = await deps.OrgAPIDataSource.getContentTypesForOrg( org.id, ); for (const it of result) { expect(it.orgId).toBe(org.id); expect(it.orgId).not.toBe(otherOrg.id); } }, ); }); describe('getOrgUsersForGraphQL', () => { testWithFixture( 'returns GraphQLUserParents for every user in the org with a working getPermissions() method', async ({ deps, org }) => { const adminId = uid(); const analystId = uid(); await kyselyUserInsert({ db: deps.KyselyPg, id: adminId, orgId: org.id, email: faker.internet.email(), firstName: faker.name.firstName(), lastName: faker.name.lastName(), role: UserRole.ADMIN, loginMethods: ['saml'], password: null, }); await kyselyUserInsert({ db: deps.KyselyPg, id: analystId, orgId: org.id, email: faker.internet.email(), firstName: faker.name.firstName(), lastName: faker.name.lastName(), role: UserRole.ANALYST, loginMethods: ['saml'], password: null, }); const users = await deps.OrgAPIDataSource.getOrgUsersForGraphQL(org.id); const ids = users.map((u) => u.id).sort(); expect(ids).toEqual([adminId, analystId].sort()); const admin = users.find((u) => u.id === adminId)!; const analyst = users.find((u) => u.id === analystId)!; expect(admin.getPermissions()).toEqual( expect.arrayContaining(['EDIT_MRT_QUEUES']), ); expect(analyst.getPermissions()).not.toContain('EDIT_MRT_QUEUES'); }, ); testWithFixture( 'returns an empty array for an org with no users', async ({ deps, org }) => { const result = await deps.OrgAPIDataSource.getOrgUsersForGraphQL( org.id, ); expect(result).toEqual([]); }, ); testWithFixture( 'does not leak users across orgs', async ({ deps, org }) => { const { org: otherOrg } = await createOrg( { KyselyPg: deps.KyselyPg, ModerationConfigService: deps.ModerationConfigService, ApiKeyService: deps.ApiKeyService, }, uid(), ); const otherUserId = uid(); await kyselyUserInsert({ db: deps.KyselyPg, id: otherUserId, orgId: otherOrg.id, email: faker.internet.email(), firstName: faker.name.firstName(), lastName: faker.name.lastName(), role: UserRole.ADMIN, loginMethods: ['saml'], password: null, }); const result = await deps.OrgAPIDataSource.getOrgUsersForGraphQL( org.id, ); expect(result.find((u) => u.id === otherUserId)).toBeUndefined(); }, ); }); });