Skip to content

Commit 6ceb7ab

Browse files
committed
Add Create Address and Test
1 parent 0d9f829 commit 6ceb7ab

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

src/action/address/create-address.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Address, User } from '@prisma/client'
2+
import { CreateAddressRequest } from '../../model/address-model'
3+
import { Validation } from '../../validation/validation'
4+
import { AddressValidation } from '../../validation/address-validation'
5+
import { prismaClient } from '../../application/database'
6+
import GetContact from '../contact/get-contact'
7+
8+
export default class CreateAddress {
9+
static async execute(
10+
user: User,
11+
request: CreateAddressRequest
12+
): Promise<Address> {
13+
const validated = Validation.validate(AddressValidation.CREATE, request)
14+
15+
await GetContact.execute(user, request.contact_id)
16+
17+
const createdAddress = await prismaClient.address.create({
18+
data: validated
19+
})
20+
21+
return createdAddress
22+
}
23+
}

src/controller/address-controller.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NextFunction, Response } from 'express'
2+
import CreateAddress from '../action/address/create-address'
3+
import UserRequest from '../request/user-request'
4+
import { CreateAddressRequest, toAddressResponse } from '../model/address-model'
5+
6+
export default class AddressController {
7+
static async create(req: UserRequest, res: Response, next: NextFunction) {
8+
try {
9+
const data = req.body as CreateAddressRequest
10+
11+
data.contact_id = BigInt(req.params.contactId)
12+
13+
const response = await CreateAddress.execute(req.user!, data)
14+
15+
res.json(toAddressResponse(response)).status(200)
16+
} catch (e: unknown) {
17+
next(e)
18+
}
19+
}
20+
}

src/model/address-model.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Address } from '@prisma/client'
2+
import { Response } from './model'
3+
import { Pagination, isPagination } from './page-model'
4+
5+
export type CreateAddressRequest = {
6+
contact_id: bigint
7+
street?: string | null
8+
city?: string | null
9+
province?: string | null
10+
country: string
11+
postal_code: string
12+
}
13+
14+
export type AddressResponse = {
15+
id: bigint
16+
street: string | null
17+
city: string | null
18+
province: string | null
19+
country: string
20+
postal_code: string
21+
}
22+
23+
export function toAddressResponse(
24+
address: Address | Pagination<Address[]>
25+
): Response<AddressResponse> | Pagination<AddressResponse[]> {
26+
const callback = (address: Address) => ({
27+
id: address.id,
28+
29+
street: address.street,
30+
city: address.city,
31+
province: address.province,
32+
country: address.country,
33+
postal_code: address.postal_code
34+
})
35+
36+
if (isPagination<Address | Address[]>(address)) {
37+
return {
38+
data: address.data.map(callback),
39+
page: address.page
40+
}
41+
}
42+
43+
return {
44+
data: callback(address)
45+
}
46+
}

src/route/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import express from 'express'
22
import { UserController } from '../controller/user-controller'
33
import { user } from '../middleware/user'
44
import ContactController from '../controller/contact-controller'
5+
import AddressController from '../controller/address-controller'
56

67
export const apiRouter = express.Router()
78
apiRouter.use(user)
@@ -15,3 +16,5 @@ apiRouter.get('/api/contacts/:contactId', ContactController.get)
1516
apiRouter.put('/api/contacts/:contactId', ContactController.update)
1617
apiRouter.delete('/api/contacts/:contactId', ContactController.delete)
1718
apiRouter.post('/api/contacts/search', ContactController.search)
19+
20+
apiRouter.post('/api/contacts/:contactId/addresses', AddressController.create)

src/validation/address-validation.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ZodType, z } from 'zod'
2+
3+
export class AddressValidation {
4+
static readonly CREATE: ZodType = z.object({
5+
contact_id: z.bigint(),
6+
street: z.string().min(0).max(255).optional(),
7+
city: z.string().min(0).max(255).optional(),
8+
province: z.string().min(0).max(255).optional(),
9+
country: z.string().min(0).max(255),
10+
postal_code: z.string().min(0).max(255)
11+
})
12+
}

tests/address.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import supertest from 'supertest'
2+
import { web } from '../src/application/web'
3+
import { createContact } from './fixtures/contact'
4+
import { createAddressRequest } from './fixtures/address'
5+
import { createUser } from './fixtures/user'
6+
import { AddressResponse } from '../src/model/address-model'
7+
import { Response } from '../src/model/model'
8+
import { faker } from '@faker-js/faker'
9+
10+
describe('POST /api/contacts/{:contactId}/addresses', () => {
11+
it('can create address by contact', async () => {
12+
const user = await createUser()
13+
14+
const contact = await createContact(user)
15+
16+
const request = createAddressRequest(contact)
17+
18+
const response = await supertest(web)
19+
.post(`/api/contacts/${contact.id}/addresses`)
20+
.set('X-API-TOKEN', user.token!)
21+
.send(request)
22+
23+
expect(response.status).toBe(200)
24+
25+
const body: Response<AddressResponse> = response.body
26+
27+
expect(body.data.id).not.toBeNull()
28+
expect(body.data.city).toBe(request.city)
29+
expect(body.data.province).toBe(request.province)
30+
expect(body.data.country).toBe(request.country)
31+
expect(body.data.postal_code).toBe(request.postal_code)
32+
expect(body.data.street).toBe(request.street)
33+
})
34+
35+
it('can create address partial by contact', async () => {
36+
const user = await createUser()
37+
38+
const contact = await createContact(user)
39+
40+
const request = createAddressRequest(contact)
41+
42+
const response = await supertest(web)
43+
.post(`/api/contacts/${contact.id}/addresses`)
44+
.set('X-API-TOKEN', user.token!)
45+
.send({
46+
street: request.street,
47+
country: request.country,
48+
postal_code: request.postal_code
49+
})
50+
51+
expect(response.status).toBe(200)
52+
53+
const body: Response<AddressResponse> = response.body
54+
55+
expect(body.data.id).not.toBeNull()
56+
expect(body.data.city).toBeNull()
57+
expect(body.data.province).toBeNull()
58+
expect(body.data.street).toBe(request.street)
59+
expect(body.data.country).toBe(request.country)
60+
expect(body.data.postal_code).toBe(request.postal_code)
61+
})
62+
63+
it('cant create address if payload is not desirable', async () => {
64+
const user = await createUser()
65+
66+
const contact = await createContact(user)
67+
68+
const response = await supertest(web)
69+
.post(`/api/contacts/${contact.id}/addresses`)
70+
.set('X-API-TOKEN', user.token!)
71+
.send({
72+
street: faker.word.words(50),
73+
country: faker.word.words(50),
74+
postal_code: faker.word.words(50),
75+
province: faker.word.words(50),
76+
city: faker.word.words(50)
77+
})
78+
79+
expect(response.status).toBe(422)
80+
})
81+
82+
it('can create address if contact not available', async () => {
83+
const user = await createUser()
84+
85+
const contact = await createContact(user)
86+
87+
const response = await supertest(web)
88+
.post(`/api/contacts/0/addresses`)
89+
.set('X-API-TOKEN', user.token!)
90+
.send(createAddressRequest(contact))
91+
92+
expect(response.status).toBe(404)
93+
})
94+
})

tests/fixtures/address.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { faker } from '@faker-js/faker'
2+
import { CreateAddressRequest } from '../../src/model/address-model'
3+
import { Contact, User } from '@prisma/client'
4+
import CreateAddress from '../../src/action/address/create-address'
5+
6+
export const createAddressRequest = (
7+
contact: Contact,
8+
state?: Partial<CreateAddressRequest>
9+
): CreateAddressRequest => ({
10+
contact_id: contact.id,
11+
street: faker.location.streetAddress(),
12+
city: faker.location.city(),
13+
province: faker.location.state(),
14+
country: faker.location.country(),
15+
postal_code: faker.location.zipCode(),
16+
...state
17+
})
18+
19+
export const createAddress = async (user: User, contact: Contact) => {
20+
const request = createAddressRequest(contact)
21+
22+
return await CreateAddress.execute(user, request)
23+
}

0 commit comments

Comments
 (0)