add tests for create/delete labels

This commit is contained in:
Hongbo Wu 2022-02-21 16:53:04 +08:00
parent e74f334db9
commit 9150e7dbc2
7 changed files with 94 additions and 92 deletions

View file

@ -31,7 +31,7 @@ export class Label extends BaseEntity {
@Column('text')
color!: string
@Column('text')
@Column('text', { nullable: true })
description?: string
@CreateDateColumn()

View file

@ -11,6 +11,7 @@ import {
import { MembershipTier, RegistrationType } from '../datalayer/user/model'
import { NewsletterEmail } from './newsletter_email'
import { Profile } from './profile'
import { Label } from './label'
@Entity()
export class User extends BaseEntity {
@ -46,4 +47,7 @@ export class User extends BaseEntity {
@Column('varchar', { length: 255, nullable: true })
password?: string
@OneToMany(() => Label, (label) => label.user)
labels?: Label[]
}

View file

@ -276,12 +276,14 @@ export type CreateLabelError = {
export enum CreateLabelErrorCode {
BadRequest = 'BAD_REQUEST',
LabelAlreadyExists = 'LABEL_ALREADY_EXISTS',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED'
}
export type CreateLabelInput = {
linkId: Scalars['ID'];
color: Scalars['String'];
description?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
};
@ -624,6 +626,9 @@ export type HighlightStats = {
export type Label = {
__typename?: 'Label';
color: Scalars['String'];
createdAt: Scalars['Date'];
description?: Maybe<Scalars['String']>;
id: Scalars['ID'];
name: Scalars['String'];
};
@ -1120,11 +1125,6 @@ export type QueryGetFollowingArgs = {
};
export type QueryLabelsArgs = {
linkId: Scalars['ID'];
};
export type QueryReminderArgs = {
linkId: Scalars['ID'];
};
@ -2762,6 +2762,9 @@ export type HighlightStatsResolvers<ContextType = ResolverContext, ParentType ex
};
export type LabelResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Label'] = ResolversParentTypes['Label']> = {
color?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -2955,7 +2958,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
getFollowing?: Resolver<ResolversTypes['GetFollowingResult'], ParentType, ContextType, Partial<QueryGetFollowingArgs>>;
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
labels?: Resolver<ResolversTypes['LabelsResult'], ParentType, ContextType, RequireFields<QueryLabelsArgs, 'linkId'>>;
labels?: Resolver<ResolversTypes['LabelsResult'], ParentType, ContextType>;
me?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType>;
newsletterEmails?: Resolver<ResolversTypes['NewsletterEmailsResult'], ParentType, ContextType>;
reminder?: Resolver<ResolversTypes['ReminderResult'], ParentType, ContextType, RequireFields<QueryReminderArgs, 'linkId'>>;

View file

@ -234,12 +234,14 @@ type CreateLabelError {
enum CreateLabelErrorCode {
BAD_REQUEST
LABEL_ALREADY_EXISTS
NOT_FOUND
UNAUTHORIZED
}
input CreateLabelInput {
linkId: ID!
color: String!
description: String
name: String!
}
@ -548,6 +550,9 @@ type HighlightStats {
}
type Label {
color: String!
createdAt: Date!
description: String
id: ID!
name: String!
}
@ -787,7 +792,7 @@ type Query {
getFollowing(userId: ID): GetFollowingResult!
getUserPersonalization: GetUserPersonalizationResult!
hello: String
labels(linkId: ID!): LabelsResult!
labels: LabelsResult!
me: User
newsletterEmails: NewsletterEmailsResult!
reminder(linkId: ID!): ReminderResult!

View file

@ -11,57 +11,47 @@ import {
LabelsSuccess,
MutationCreateLabelArgs,
MutationDeleteLabelArgs,
QueryLabelsArgs,
} from '../../generated/graphql'
import { analytics } from '../../utils/analytics'
import { env } from '../../env'
import { User } from '../../entity/user'
import { Link } from '../../entity/link'
import { Label } from '../../entity/label'
import { getManager, getRepository } from 'typeorm'
import { setClaims } from '../../entity/utils'
export const labelsResolver = authorized<
LabelsSuccess,
LabelsError,
QueryLabelsArgs
>(async (_, { linkId }, { claims: { uid }, log }) => {
log.info('labelsResolver')
export const labelsResolver = authorized<LabelsSuccess, LabelsError>(
async (_obj, _params, { claims: { uid }, log }) => {
log.info('labelsResolver')
analytics.track({
userId: uid,
event: 'labels',
properties: {
linkId: linkId,
env: env.server.apiEnv,
},
})
analytics.track({
userId: uid,
event: 'labels',
properties: {
env: env.server.apiEnv,
},
})
try {
const user = await User.findOne(uid)
if (!user) {
return {
errorCodes: [LabelsErrorCode.Unauthorized],
try {
const user = await User.findOne(uid, {
relations: ['labels'],
})
if (!user) {
return {
errorCodes: [LabelsErrorCode.Unauthorized],
}
}
}
const link = await Link.findOne(linkId, { relations: ['labels'] })
if (!link) {
return {
errorCodes: [LabelsErrorCode.NotFound],
labels: user.labels || [],
}
} catch (error) {
log.error(error)
return {
errorCodes: [LabelsErrorCode.BadRequest],
}
}
return {
labels: link.labels || [],
}
} catch (error) {
log.error(error)
return {
errorCodes: [LabelsErrorCode.BadRequest],
}
}
})
)
export const createLabelResolver = authorized<
CreateLabelSuccess,
@ -70,7 +60,7 @@ export const createLabelResolver = authorized<
>(async (_, { input }, { claims: { uid }, log }) => {
log.info('createLabelResolver')
const { linkId, name } = input
const { name, color, description } = input
try {
const user = await getRepository(User).findOne(uid)
@ -80,18 +70,23 @@ export const createLabelResolver = authorized<
}
}
const link = await getRepository(Link).findOne(linkId)
if (!link) {
const existingLabel = await getRepository(Label).findOne({
where: {
name,
},
})
if (existingLabel) {
return {
errorCodes: [CreateLabelErrorCode.NotFound],
errorCodes: [CreateLabelErrorCode.LabelAlreadyExists],
}
}
const label = await getRepository(Label)
.create({
user,
link,
name,
color,
description: description || '',
})
.save()
@ -99,8 +94,9 @@ export const createLabelResolver = authorized<
userId: uid,
event: 'createLabel',
properties: {
linkId,
name,
color,
description,
env: env.server.apiEnv,
},
})

View file

@ -1242,6 +1242,9 @@ const schema = gql`
type Label {
id: ID!
name: String!
color: String!
description: String
createdAt: Date!
}
type LabelsSuccess {
@ -1261,8 +1264,9 @@ const schema = gql`
union LabelsResult = LabelsSuccess | LabelsError
input CreateLabelInput {
linkId: ID!
name: String!
color: String!
description: String
}
type CreateLabelSuccess {
@ -1273,6 +1277,7 @@ const schema = gql`
UNAUTHORIZED
BAD_REQUEST
NOT_FOUND
LABEL_ALREADY_EXISTS
}
type CreateLabelError {
@ -1414,7 +1419,7 @@ const schema = gql`
articleSavingRequest(id: ID!): ArticleSavingRequestResult!
newsletterEmails: NewsletterEmailsResult!
reminder(linkId: ID!): ReminderResult!
labels(linkId: ID!): LabelsResult!
labels: LabelsResult!
}
`

View file

@ -36,14 +36,14 @@ describe('Labels API', () => {
.create({
name: 'label1',
user: user,
link: link,
color: '#ffffff',
})
.save()
const label2 = await getRepository(Label)
.create({
name: 'label2',
user: user,
link: link,
color: '#eeeeee',
})
.save()
labels = [label1, label2]
@ -56,16 +56,18 @@ describe('Labels API', () => {
describe('GET labels', () => {
let query: string
let linkId: string
beforeEach(() => {
query = `
query {
labels(linkId: "${linkId}") {
labels {
... on LabelsSuccess {
labels {
id
name
color
description
createdAt
}
}
... on LabelsError {
@ -76,33 +78,18 @@ describe('Labels API', () => {
`
})
context('when link exists', () => {
before(() => {
linkId = link.id
})
it('should return labels', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
it('should return labels', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.labels.labels).to.eql(
labels.map((label) => ({
id: label.id,
name: label.name,
}))
)
})
})
context('when link not exist', () => {
before(() => {
linkId = generateFakeUuid()
})
it('should return error code NOT_FOUND', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.labels.errorCodes).to.eql(['NOT_FOUND'])
})
expect(res.body.data.labels.labels).to.eql(
labels.map((label) => ({
id: label.id,
name: label.name,
color: label.color,
description: label.description,
createdAt: new Date(label.createdAt.setMilliseconds(0)).toISOString(),
}))
)
})
it('responds status code 400 when invalid query', async () => {
@ -122,15 +109,15 @@ describe('Labels API', () => {
describe('Create label', () => {
let query: string
let linkId: string
let name: string
beforeEach(() => {
query = `
mutation {
createLabel(
input: {
linkId: "${linkId}",
name: "label3"
color: "#ffffff"
name: "${name}"
}
) {
... on CreateLabelSuccess {
@ -147,9 +134,9 @@ describe('Labels API', () => {
`
})
context('when link exists', () => {
context('when name not exists', () => {
before(() => {
linkId = link.id
name = 'label3'
})
it('should create label', async () => {
@ -161,15 +148,17 @@ describe('Labels API', () => {
})
})
context('when link not exist', () => {
context('when name exists', () => {
before(() => {
linkId = generateFakeUuid()
name = labels[0].name
})
it('should return error code NOT_FOUND', async () => {
it('should return error code LABEL_ALREADY_EXISTS', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.createLabel.errorCodes).to.eql(['NOT_FOUND'])
expect(res.body.data.createLabel.errorCodes).to.eql([
'LABEL_ALREADY_EXISTS',
])
})
})