Ulf Gebhardt 45d2283138
refactor(backend): externalize all remaining queries in spec files (#8902)
* refactor: externalize all remaining queries in spec files

* User with different queries due to permissions

* fix notification:groups

* fix hashtagsmiddleware

* fix blockedUsers

* fix softDeleteMiddleware

* fix shouts.spec

* fix userInteractions spec

* fix mutedUsers spec

* seocialMedia spec

* fix notificationMiddleware.spec

* fix user.spce & fix undefined activeCategories

* fix notifications.spec

* fix userInteractions.spec

* fix blockedUsers & mutedUsers spec

* remove unused comment

* fix locations spec

* fix orderByMiddleware & spec

* fix lint

* fix shout spec
2025-09-18 17:43:15 +02:00

1121 lines
32 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
import Factory, { cleanDatabase } from '@db/factories'
import { revokeBadge } from '@graphql/queries/revokeBadge'
import { rewardTrophyBadge } from '@graphql/queries/rewardTrophyBadge'
import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected'
import { setVerificationBadge } from '@graphql/queries/setVerificationBadge'
import { User } from '@graphql/queries/User'
import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context'
let regularUser, administrator, moderator, badge, verification
let authenticatedUser: Context['user']
const context = () => ({ authenticatedUser })
let mutate: ApolloTestSetup['mutate']
let query: ApolloTestSetup['query']
let database: ApolloTestSetup['database']
let server: ApolloTestSetup['server']
beforeAll(async () => {
await cleanDatabase()
const apolloSetup = createApolloTestSetup({ context })
mutate = apolloSetup.mutate
query = apolloSetup.query
database = apolloSetup.database
server = apolloSetup.server
})
afterAll(() => {
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('Badges', () => {
beforeEach(async () => {
regularUser = await Factory.build(
'user',
{
id: 'regular-user-id',
role: 'user',
},
{
email: 'user@example.org',
password: '1234',
},
)
moderator = await Factory.build(
'user',
{
id: 'moderator-id',
role: 'moderator',
},
{
email: 'moderator@example.org',
},
)
administrator = await Factory.build(
'user',
{
id: 'admin-id',
role: 'admin',
},
{
email: 'admin@example.org',
},
)
badge = await Factory.build('badge', {
id: 'trophy_rhino',
type: 'trophy',
description: 'You earned a rhino',
icon: '/img/badges/trophy_blue_rhino.svg',
})
verification = await Factory.build('badge', {
id: 'verification_moderator',
type: 'verification',
description: 'You are a moderator',
icon: '/img/badges/verification_red_moderator.svg',
})
})
afterEach(async () => {
await cleanDatabase()
})
describe('setVerificationBadge', () => {
const variables = {
badgeId: 'verification_moderator',
userId: 'regular-user-id',
}
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(mutate({ mutation: setVerificationBadge, variables })).resolves.toMatchObject({
data: { setVerificationBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
describe('authenticated as moderator', () => {
beforeEach(() => {
authenticatedUser = moderator.toJson()
})
describe('rewards badge to user', () => {
it('throws authorization error', async () => {
await expect(
mutate({ mutation: setVerificationBadge, variables }),
).resolves.toMatchObject({
data: { setVerificationBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
})
describe('authenticated as admin', () => {
beforeEach(async () => {
authenticatedUser = await administrator.toJson()
})
describe('badge for id does not exist', () => {
it('rejects with an informative error message', async () => {
await expect(
mutate({
mutation: setVerificationBadge,
variables: { userId: 'regular-user-id', badgeId: 'non-existent-badge-id' },
}),
).resolves.toMatchObject({
data: { setVerificationBadge: null },
errors: [
{
message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
describe('non-existent user', () => {
it('rejects with a telling error message', async () => {
await expect(
mutate({
mutation: setVerificationBadge,
variables: { userId: 'non-existent-user-id', badgeId: 'verification_moderator' },
}),
).resolves.toMatchObject({
data: { setVerificationBadge: null },
errors: [
{
message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
describe('badge is not a verification badge', () => {
it('rejects with a telling error message', async () => {
await expect(
mutate({
mutation: setVerificationBadge,
variables: { userId: 'regular-user-id', badgeId: 'trophy_rhino' },
}),
).resolves.toMatchObject({
data: { setVerificationBadge: null },
errors: [
{
message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
it('rewards a verification badge to the user', async () => {
const expected = {
data: {
setVerificationBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'verification_moderator', isDefault: false },
badgeTrophies: [],
},
},
errors: undefined,
}
await expect(mutate({ mutation: setVerificationBadge, variables })).resolves.toMatchObject(
expected,
)
})
it('overrides the existing verification if a second verification badge is rewarded to the same user', async () => {
await Factory.build('badge', {
id: 'verification_admin',
type: 'verification',
description: 'You are an admin',
icon: '/img/badges/verification_red_admin.svg',
})
const expected = {
data: {
setVerificationBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'verification_admin', isDefault: false },
badgeTrophies: [],
},
},
errors: undefined,
}
await mutate({
mutation: setVerificationBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'verification_moderator',
},
})
await expect(
mutate({
mutation: setVerificationBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'verification_admin',
},
}),
).resolves.toMatchObject(expected)
})
it('rewards the same verification badge as well to another user', async () => {
const expected = {
data: {
setVerificationBadge: {
id: 'regular-user-2-id',
badgeVerification: { id: 'verification_moderator', isDefault: false },
badgeTrophies: [],
},
},
errors: undefined,
}
await Factory.build(
'user',
{
id: 'regular-user-2-id',
},
{
email: 'regular2@email.com',
},
)
await mutate({
mutation: setVerificationBadge,
variables,
})
await expect(
mutate({
mutation: setVerificationBadge,
variables: {
userId: 'regular-user-2-id',
badgeId: 'verification_moderator',
},
}),
).resolves.toMatchObject(expected)
})
})
})
describe('rewardTrophyBadge', () => {
const variables = {
badgeId: 'trophy_rhino',
userId: 'regular-user-id',
}
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(mutate({ mutation: rewardTrophyBadge, variables })).resolves.toMatchObject({
data: { rewardTrophyBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
describe('authenticated as moderator', () => {
beforeEach(() => {
authenticatedUser = moderator.toJson()
})
describe('rewards badge to user', () => {
it('throws authorization error', async () => {
await expect(mutate({ mutation: rewardTrophyBadge, variables })).resolves.toMatchObject({
data: { rewardTrophyBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
})
describe('authenticated as admin', () => {
beforeEach(async () => {
authenticatedUser = await administrator.toJson()
})
describe('badge for id does not exist', () => {
it('rejects with an informative error message', async () => {
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: { userId: 'regular-user-id', badgeId: 'non-existent-badge-id' },
}),
).resolves.toMatchObject({
data: { rewardTrophyBadge: null },
errors: [
{
message:
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
describe('non-existent user', () => {
it('rejects with a telling error message', async () => {
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: { userId: 'non-existent-user-id', badgeId: 'trophy_rhino' },
}),
).resolves.toMatchObject({
data: { rewardTrophyBadge: null },
errors: [
{
message:
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
describe('badge is a verification Badge', () => {
it('rejects with a telling error message', async () => {
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: { userId: 'regular-user-id', badgeId: 'verification_moderator' },
}),
).resolves.toMatchObject({
data: { rewardTrophyBadge: null },
errors: [
{
message:
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
},
],
})
})
})
it('rewards a badge to the user', async () => {
await expect(mutate({ mutation: rewardTrophyBadge, variables })).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'default_verification', isDefault: true },
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeTrophiesSelected: [
{ id: 'trophy_rhino' },
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
it('rewards a second different badge to the same user', async () => {
await Factory.build('badge', {
id: 'trophy_racoon',
type: 'trophy',
description: 'You earned a racoon',
icon: '/img/badges/trophy_blue_racoon.svg',
})
await mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_racoon',
},
})
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_rhino',
},
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeTrophies: expect.arrayContaining([
{ id: 'trophy_racoon' },
{ id: 'trophy_rhino' },
]),
badgeTrophiesSelected: [
{ id: 'trophy_racoon' },
{
id: 'trophy_rhino',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
it('does not select a badge again when already rewarded and unselected by the user', async () => {
await Factory.build('badge', {
id: 'trophy_racoon',
type: 'trophy',
description: 'You earned a racoon',
icon: '/img/badges/trophy_blue_racoon.svg',
})
await mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_rhino',
},
})
await mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_racoon',
},
})
authenticatedUser = await regularUser.toJson()
await mutate({
mutation: setTrophyBadgeSelected,
variables: {
slot: 0,
badgeId: null,
},
})
authenticatedUser = await administrator.toJson()
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_rhino',
},
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeTrophies: expect.arrayContaining([
{ id: 'trophy_racoon' },
{ id: 'trophy_rhino' },
]),
badgeTrophiesSelected: [
{
id: 'default_trophy',
},
{ id: 'trophy_racoon' },
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
it('does fill gaps in the selection array when rewarding new badges', async () => {
await Factory.build('badge', {
id: 'trophy_racoon',
type: 'trophy',
description: 'You earned a racoon',
icon: '/img/badges/trophy_blue_racoon.svg',
})
await mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_rhino',
},
})
authenticatedUser = await regularUser.toJson()
await mutate({
mutation: setTrophyBadgeSelected,
variables: {
slot: 1,
badgeId: 'trophy_rhino',
},
})
authenticatedUser = await administrator.toJson()
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_racoon',
},
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeTrophies: expect.arrayContaining([
{ id: 'trophy_racoon' },
{ id: 'trophy_rhino' },
]),
badgeTrophiesSelected: [
{ id: 'trophy_racoon' },
{ id: 'trophy_rhino' },
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
it('does not select badge when maximum selected are already reached', async () => {
for (let i = 0; i < TROPHY_BADGES_SELECTED_MAX; i++) {
await Factory.build('badge', {
id: `trophy_${i}`,
type: 'trophy',
description: `You earned a ${i}`,
icon: `/img/badges/trophy_blue_${i}.svg`,
})
await mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: `trophy_${i}`,
},
})
}
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-id',
badgeId: 'trophy_rhino',
},
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeTrophies: expect.arrayContaining([
{ id: 'trophy_0' },
{ id: 'trophy_1' },
{ id: 'trophy_2' },
{ id: 'trophy_3' },
{ id: 'trophy_4' },
{ id: 'trophy_5' },
{ id: 'trophy_6' },
{ id: 'trophy_7' },
{ id: 'trophy_8' },
{ id: 'trophy_rhino' },
]),
badgeTrophiesSelected: [
{ id: 'trophy_0' },
{ id: 'trophy_1' },
{ id: 'trophy_2' },
{ id: 'trophy_3' },
{ id: 'trophy_4' },
{ id: 'trophy_5' },
{ id: 'trophy_6' },
{ id: 'trophy_7' },
{ id: 'trophy_8' },
],
},
},
errors: undefined,
})
})
it('rewards the same badge as well to another user', async () => {
await Factory.build(
'user',
{
id: 'regular-user-2-id',
},
{
email: 'regular2@email.com',
},
)
await mutate({
mutation: rewardTrophyBadge,
variables,
})
await expect(
mutate({
mutation: rewardTrophyBadge,
variables: {
userId: 'regular-user-2-id',
badgeId: 'trophy_rhino',
},
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-2-id',
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeTrophiesSelected: [
{ id: 'trophy_rhino' },
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
it('creates no duplicate reward relationships', async () => {
await mutate({
mutation: rewardTrophyBadge,
variables,
})
await expect(
mutate({
mutation: rewardTrophyBadge,
variables,
}),
).resolves.toMatchObject({
data: {
rewardTrophyBadge: {
id: 'regular-user-id',
badgeTrophiesCount: 1,
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeTrophiesSelected: [
{ id: 'trophy_rhino' },
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
{
id: 'default_trophy',
},
],
},
},
errors: undefined,
})
})
})
})
describe('revokeBadge', () => {
const variables = {
badgeId: 'trophy_rhino',
userId: 'regular-user-id',
}
beforeEach(async () => {
await regularUser.relateTo(badge, 'rewarded')
await regularUser.relateTo(verification, 'verifies')
await regularUser.relateTo(badge, 'selected', { slot: 6 })
})
describe('check test setup', () => {
it('user has one badge and has it selected', async () => {
authenticatedUser = await regularUser.toJson()
const expected = {
data: {
User: [
{
badgeTrophiesCount: 1,
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeVerification: {
id: 'verification_moderator',
isDefault: false,
},
badgeTrophiesSelected: [
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'trophy_rhino',
isDefault: false,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
],
},
],
},
errors: undefined,
}
await expect(
query({ query: User, variables: { id: 'regular-user-id' } }),
).resolves.toMatchObject(expected)
})
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(mutate({ mutation: revokeBadge, variables })).resolves.toMatchObject({
data: { revokeBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
describe('authenticated moderator', () => {
beforeEach(async () => {
authenticatedUser = await moderator.toJson()
})
describe('removes badge from user', () => {
it('throws authorization error', async () => {
await expect(mutate({ mutation: revokeBadge, variables })).resolves.toMatchObject({
data: { revokeBadge: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
})
describe('authenticated admin', () => {
beforeEach(async () => {
authenticatedUser = await administrator.toJson()
})
it('removes a badge from user', async () => {
await expect(mutate({ mutation: revokeBadge, variables })).resolves.toMatchObject({
data: {
revokeBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'verification_moderator', isDefault: false },
badgeTrophies: [],
badgeTrophiesSelected: [
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
],
},
},
errors: undefined,
})
})
it('does not crash when revoking multiple times', async () => {
await mutate({ mutation: revokeBadge, variables })
await expect(mutate({ mutation: revokeBadge, variables })).resolves.toMatchObject({
data: {
revokeBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'verification_moderator', isDefault: false },
badgeTrophies: [],
badgeTrophiesSelected: [
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
],
},
},
errors: undefined,
})
})
it('removes a verification from user', async () => {
await expect(
mutate({
mutation: revokeBadge,
variables: {
badgeId: 'verification_moderator',
userId: 'regular-user-id',
},
}),
).resolves.toMatchObject({
data: {
revokeBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'default_verification', isDefault: true },
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeTrophiesSelected: [
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'trophy_rhino',
isDefault: false,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
],
},
},
errors: undefined,
})
})
it('does not crash when removing verification multiple times', async () => {
await mutate({
mutation: revokeBadge,
variables: {
badgeId: 'verification_moderator',
userId: 'regular-user-id',
},
})
await expect(
mutate({
mutation: revokeBadge,
variables: {
badgeId: 'verification_moderator',
userId: 'regular-user-id',
},
}),
).resolves.toMatchObject({
data: {
revokeBadge: {
id: 'regular-user-id',
badgeVerification: { id: 'default_verification', isDefault: true },
badgeTrophies: [{ id: 'trophy_rhino' }],
badgeTrophiesSelected: [
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'trophy_rhino',
isDefault: false,
},
{
id: 'default_trophy',
isDefault: true,
},
{
id: 'default_trophy',
isDefault: true,
},
],
},
},
errors: undefined,
})
})
})
})
})