Merge remote-tracking branch 'origin/master' into 1798-feature-gradidoid-1-adapt-and-migrate-database-schema

This commit is contained in:
Claus-Peter Hübner 2022-07-19 21:11:28 +02:00
commit 4951a684c6
7 changed files with 207 additions and 34 deletions

View File

@ -26,6 +26,7 @@ export enum RIGHTS {
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
GDT_BALANCE = 'GDT_BALANCE',
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
DELETE_CONTRIBUTION = 'DELETE_CONTRIBUTION',
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS',
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',

View File

@ -24,6 +24,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.LIST_TRANSACTION_LINKS,
RIGHTS.GDT_BALANCE,
RIGHTS.CREATE_CONTRIBUTION,
RIGHTS.DELETE_CONTRIBUTION,
RIGHTS.LIST_CONTRIBUTIONS,
RIGHTS.LIST_ALL_CONTRIBUTIONS,
RIGHTS.UPDATE_CONTRIBUTION,

View File

@ -48,13 +48,13 @@ export class Contribution {
@ObjectType()
export class ContributionListResult {
constructor(count: number, list: Contribution[]) {
this.linkCount = count
this.linkList = list
this.contributionCount = count
this.contributionList = list
}
@Field(() => Int)
linkCount: number
contributionCount: number
@Field(() => [Contribution])
linkList: Contribution[]
contributionList: Contribution[]
}

View File

@ -4,7 +4,9 @@
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import {
adminUpdateContribution,
confirmContribution,
createContribution,
deleteContribution,
updateContribution,
} from '@/seeds/graphql/mutations'
import { listAllContributions, listContributions, login } from '@/seeds/graphql/queries'
@ -193,18 +195,21 @@ describe('ContributionResolver', () => {
).resolves.toEqual(
expect.objectContaining({
data: {
listContributions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
listContributions: {
contributionCount: 2,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
@ -226,13 +231,16 @@ describe('ContributionResolver', () => {
).resolves.toEqual(
expect.objectContaining({
data: {
listContributions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
listContributions: {
contributionCount: 1,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
},
},
}),
)
@ -481,6 +489,11 @@ describe('ContributionResolver', () => {
})
})
afterAll(async () => {
await cleanDB()
resetToken()
})
it('returns allCreation', async () => {
await expect(
query({
@ -496,8 +509,8 @@ describe('ContributionResolver', () => {
expect.objectContaining({
data: {
listAllContributions: {
linkCount: 2,
linkList: expect.arrayContaining([
contributionCount: 2,
contributionList: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Herzlich Willkommen bei Gradido!',
@ -516,4 +529,129 @@ describe('ContributionResolver', () => {
})
})
})
describe('deleteContribution', () => {
describe('unauthenticated', () => {
it('returns an error', async () => {
await expect(
query({
query: deleteContribution,
variables: {
id: -1,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('authenticated', () => {
beforeAll(async () => {
await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
await query({
query: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
result = await mutate({
mutation: createContribution,
variables: {
amount: 100.0,
memo: 'Test env contribution',
creationDate: new Date().toString(),
},
})
})
afterAll(async () => {
await cleanDB()
resetToken()
})
describe('wrong contribution id', () => {
it('returns an error', async () => {
await expect(
mutate({
mutation: deleteContribution,
variables: {
id: -1,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Contribution not found for given id.')],
}),
)
})
})
describe('other user sends a deleteContribtuion', () => {
it('returns an error', async () => {
await query({
query: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
await expect(
mutate({
mutation: deleteContribution,
variables: {
id: result.data.createContribution.id,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Can not delete contribution of another user')],
}),
)
})
})
describe('User deletes own contribution', () => {
it('deletes successfully', async () => {
await expect(
mutate({
mutation: deleteContribution,
variables: {
id: result.data.createContribution.id,
},
}),
).resolves.toBeTruthy()
})
})
describe('User deletes already confirmed contribution', () => {
it('throws an error', async () => {
await query({
query: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
})
await mutate({
mutation: confirmContribution,
variables: {
id: result.data.createContribution.id,
},
})
await query({
query: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
await expect(
mutate({
mutation: deleteContribution,
variables: {
id: result.data.createContribution.id,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('A confirmed contribution can not be deleted')],
}),
)
})
})
})
})
})

View File

@ -38,22 +38,43 @@ export class ContributionResolver {
return new UnconfirmedContribution(contribution, user, creations)
}
@Authorized([RIGHTS.DELETE_CONTRIBUTION])
@Mutation(() => Boolean)
async deleteContribution(
@Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise<boolean> {
const user = getUser(context)
const contribution = await dbContribution.findOne(id)
if (!contribution) {
throw new Error('Contribution not found for given id.')
}
if (contribution.userId !== user.id) {
throw new Error('Can not delete contribution of another user')
}
if (contribution.confirmedAt) {
throw new Error('A confirmed contribution can not be deleted')
}
const res = await contribution.softRemove()
return !!res
}
@Authorized([RIGHTS.LIST_CONTRIBUTIONS])
@Query(() => [Contribution])
@Query(() => ContributionListResult)
async listContributions(
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Arg('filterConfirmed', () => Boolean)
filterConfirmed: boolean | null,
@Ctx() context: Context,
): Promise<Contribution[]> {
): Promise<ContributionListResult> {
const user = getUser(context)
const where: {
userId: number
confirmedBy?: FindOperator<number> | null
} = { userId: user.id }
if (filterConfirmed) where.confirmedBy = IsNull()
const contributions = await dbContribution.find({
const [contributions, count] = await dbContribution.findAndCount({
where,
order: {
createdAt: order,
@ -62,7 +83,10 @@ export class ContributionResolver {
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return contributions.map((contribution) => new Contribution(contribution, new User(user)))
return new ContributionListResult(
count,
contributions.map((contribution) => new Contribution(contribution, new User(user))),
)
}
@Authorized([RIGHTS.LIST_ALL_CONTRIBUTIONS])

View File

@ -255,3 +255,9 @@ export const updateContribution = gql`
}
}
`
export const deleteContribution = gql`
mutation ($id: Int!) {
deleteContribution(id: $id)
}
`

View File

@ -185,9 +185,12 @@ export const listContributions = gql`
order: $order
filterConfirmed: $filterConfirmed
) {
id
amount
memo
contributionCount
contributionList {
id
amount
memo
}
}
}
`
@ -195,8 +198,8 @@ export const listContributions = gql`
export const listAllContributions = `
query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC) {
listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
linkCount
linkList {
contributionCount
contributionList {
id
firstName
lastName