Skip to content

Commit 94f0203

Browse files
authored
Merge pull request #314 from Bostads-AB-Mimer/feature/mim-572-skapa-endpoint-for-att-hamta-byggnad
MIM-572: Create endpoint for getting building by code - Core
2 parents e319cb9 + ebfa7cf commit 94f0203

File tree

9 files changed

+328
-6
lines changed

9 files changed

+328
-6
lines changed

src/adapters/property-base-adapter/generated/api-types.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,67 @@ export interface paths {
446446
patch?: never;
447447
trace?: never;
448448
};
449+
"/buildings/by-building-code/{buildingCode}": {
450+
parameters: {
451+
query?: never;
452+
header?: never;
453+
path?: never;
454+
cookie?: never;
455+
};
456+
/**
457+
* Get detailed information about a specific building by building code
458+
* @description Retrieves comprehensive information about a building using its building code.
459+
* Returns details including construction year, renovation history, insurance information,
460+
* and associated property data.
461+
*
462+
*/
463+
get: {
464+
parameters: {
465+
query?: never;
466+
header?: never;
467+
path: {
468+
/** @description The building code of the building */
469+
buildingCode: string;
470+
};
471+
cookie?: never;
472+
};
473+
requestBody?: never;
474+
responses: {
475+
/** @description Successfully retrieved building information */
476+
200: {
477+
headers: {
478+
[name: string]: unknown;
479+
};
480+
content: {
481+
"application/json": {
482+
content?: components["schemas"]["Building"];
483+
};
484+
};
485+
};
486+
/** @description Building not found */
487+
404: {
488+
headers: {
489+
[name: string]: unknown;
490+
};
491+
content?: never;
492+
};
493+
/** @description Internal server error */
494+
500: {
495+
headers: {
496+
[name: string]: unknown;
497+
};
498+
content?: never;
499+
};
500+
};
501+
};
502+
put?: never;
503+
post?: never;
504+
delete?: never;
505+
options?: never;
506+
head?: never;
507+
patch?: never;
508+
trace?: never;
509+
};
449510
"/buildings/{id}": {
450511
parameters: {
451512
query?: never;
@@ -1214,11 +1275,11 @@ export interface components {
12141275
Building: {
12151276
id: string;
12161277
code: string;
1217-
name: string;
1278+
name: string | null;
12181279
buildingType: {
1219-
id: string;
1220-
code: string;
1221-
name: string;
1280+
id: string | null;
1281+
code: string | null;
1282+
name: string | null;
12221283
};
12231284
construction: {
12241285
constructionYear: number | null;

src/adapters/property-base-adapter/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ export async function searchResidences(
7777
}
7878
}
7979

80+
type GetBuildingResponse = components['schemas']['Building']
81+
82+
export async function getBuildingByCode(
83+
buildingCode: string
84+
): Promise<AdapterResult<GetBuildingResponse, 'not-found' | 'unknown'>> {
85+
try {
86+
const fetchResponse = await client().GET(
87+
'/buildings/by-building-code/{buildingCode}',
88+
{
89+
params: { path: { buildingCode } },
90+
}
91+
)
92+
93+
if (fetchResponse.data?.content) {
94+
return { ok: true, data: fetchResponse.data.content }
95+
}
96+
97+
if (fetchResponse.response.status === 404) {
98+
return { ok: false, err: 'not-found' }
99+
}
100+
101+
return { ok: false, err: 'unknown' }
102+
} catch (err) {
103+
logger.error({ err }, 'property-base-adapter.getBuilding')
104+
return { ok: false, err: 'unknown' }
105+
}
106+
}
107+
80108
type GetCompaniesResponse = components['schemas']['Company'][]
81109

82110
export async function getCompanies(): Promise<

src/adapters/tests/property-base-adapter.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,56 @@ describe('property-base-adapter', () => {
2020
mockServer.close()
2121
})
2222

23+
describe('getBuildingByCode', () => {
24+
it('returns err if request fails', async () => {
25+
mockServer.use(
26+
http.get(
27+
`${config.propertyBaseService.url}/buildings/by-building-code/123-123`,
28+
() => new HttpResponse(null, { status: 500 })
29+
)
30+
)
31+
32+
const result = await propertyBaseAdapter.getBuildingByCode('123-123')
33+
expect(result.ok).toBe(false)
34+
if (!result.ok) expect(result.err).toBe('unknown')
35+
})
36+
37+
it('returns not-found if building is not found', async () => {
38+
mockServer.use(
39+
http.get(
40+
`${config.propertyBaseService.url}/buildings/by-building-code/123-123`,
41+
() => new HttpResponse(null, { status: 404 })
42+
)
43+
)
44+
45+
const result = await propertyBaseAdapter.getBuildingByCode('123-123')
46+
expect(result.ok).toBe(false)
47+
if (!result.ok) expect(result.err).toBe('not-found')
48+
})
49+
50+
it('returns building', async () => {
51+
const buildingMock = factory.building.build()
52+
mockServer.use(
53+
http.get(
54+
`${config.propertyBaseService.url}/buildings/by-building-code/123-123`,
55+
() =>
56+
HttpResponse.json(
57+
{
58+
content: buildingMock,
59+
},
60+
{ status: 200 }
61+
)
62+
)
63+
)
64+
65+
const result = await propertyBaseAdapter.getBuildingByCode('123-123')
66+
expect(result).toMatchObject({
67+
ok: true,
68+
data: buildingMock,
69+
})
70+
})
71+
})
72+
2373
describe('getCompanies', () => {
2474
it('returns err if request fails', async () => {
2575
mockServer.use(

src/services/property-base-service/index.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { logger, generateRouteMetadata } from 'onecore-utilities'
77
import { registerSchema } from '../../utils/openapi'
88
import * as schemas from './schemas'
99
import { calculateResidenceStatus } from './calculate-residence-status'
10-
import { z } from 'zod'
1110

1211
/**
1312
* @swagger
@@ -37,6 +36,88 @@ export const routes = (router: KoaRouter) => {
3736
schemas.ResidenceByRentalIdSchema
3837
)
3938

39+
/**
40+
* @swagger
41+
* /propertyBase/buildings/by-building-code/{buildingCode}:
42+
* get:
43+
* summary: Get building by building code
44+
* tags:
45+
* - Property base Service
46+
* description: Retrieves building data by building code
47+
* parameters:
48+
* - in: path
49+
* name: buildingCode
50+
* required: true
51+
* schema:
52+
* type: string
53+
* description: The code of the building
54+
* responses:
55+
* '200':
56+
* description: Successfully retrieved building
57+
* content:
58+
* application/json:
59+
* schema:
60+
* type: object
61+
* properties:
62+
* content:
63+
* $ref: '#/components/schemas/Building'
64+
* '404':
65+
* description: Building not found
66+
* content:
67+
* application/json:
68+
* schema:
69+
* type: object
70+
* properties:
71+
* error:
72+
* type: string
73+
* example: Building not found
74+
* '500':
75+
* description: Internal server error
76+
* content:
77+
* application/json:
78+
* schema:
79+
* type: object
80+
* properties:
81+
* error:
82+
* type: string
83+
* example: Internal server error
84+
* security:
85+
* - bearerAuth: []
86+
*/
87+
router.get(
88+
'(.*)/propertyBase/buildings/by-building-code/:buildingCode',
89+
async (ctx) => {
90+
const metadata = generateRouteMetadata(ctx)
91+
const { buildingCode } = ctx.params
92+
93+
try {
94+
const result = await propertyBaseAdapter.getBuildingByCode(buildingCode)
95+
96+
if (!result.ok) {
97+
if (result.err === 'not-found') {
98+
ctx.status = 404
99+
ctx.body = { error: 'Building not found', ...metadata }
100+
return
101+
}
102+
103+
logger.error(result.err, 'Internal server error', metadata)
104+
ctx.status = 500
105+
ctx.body = { error: 'Internal server error', ...metadata }
106+
return
107+
}
108+
109+
ctx.body = {
110+
content: result.data as schemas.Building,
111+
...metadata,
112+
}
113+
} catch (error) {
114+
logger.error(error, 'Internal server error', metadata)
115+
ctx.status = 500
116+
ctx.body = { error: 'Internal server error', ...metadata }
117+
}
118+
}
119+
)
120+
40121
/**
41122
* @swagger
42123
* /propertyBase/companies:

src/services/property-base-service/schemas.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
import { z } from 'zod'
22

3+
export const BuildingSchema = z.object({
4+
id: z.string(),
5+
code: z.string(),
6+
name: z.string(),
7+
buildingType: z.object({
8+
id: z.string(),
9+
code: z.string(),
10+
name: z.string(),
11+
}),
12+
construction: z.object({
13+
constructionYear: z.number(),
14+
renovationYear: z.number(),
15+
valueYear: z.number().nullable(),
16+
}),
17+
features: z.object({
18+
heating: z.string().nullable(),
19+
fireRating: z.string().nullable(),
20+
}),
21+
insurance: z.object({
22+
class: z.string().nullable(),
23+
value: z.number().nullable(),
24+
}),
25+
deleted: z.boolean(),
26+
})
27+
328
export const CompanySchema = z.object({
429
id: z.string(),
530
propertyObjectId: z.string(),
@@ -322,6 +347,7 @@ export const StaircasesQueryParamsSchema = z.object({
322347
.min(7, { message: 'buildingCode must be at least 7 characters long.' }),
323348
})
324349

350+
export type Building = z.infer<typeof BuildingSchema>
325351
export type Company = z.infer<typeof CompanySchema>
326352
export type Property = z.infer<typeof PropertySchema>
327353
export type PropertyDetails = z.infer<typeof PropertyDetailsSchema>

src/services/property-base-service/tests/index.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,51 @@ app.use(router.routes())
2828

2929
beforeEach(jest.resetAllMocks)
3030
describe('property-base-service', () => {
31+
describe('GET /propertyBase/buildings/by-building-code/:buildingCode', () => {
32+
it('returns 200 and a building by code', async () => {
33+
const buildingMock = factory.building.build()
34+
const getBuildingSpy = jest
35+
.spyOn(propertyBaseAdapter, 'getBuildingByCode')
36+
.mockResolvedValueOnce({ ok: true, data: buildingMock })
37+
38+
const res = await request(app.callback()).get(
39+
`/propertyBase/buildings/by-building-code/${buildingMock.code}`
40+
)
41+
42+
expect(res.status).toBe(200)
43+
expect(getBuildingSpy).toHaveBeenCalledWith(buildingMock.code)
44+
expect(JSON.stringify(res.body.content)).toEqual(
45+
JSON.stringify(buildingMock)
46+
)
47+
})
48+
49+
it('returns 404 if no building is found', async () => {
50+
const getBuildingSpy = jest
51+
.spyOn(propertyBaseAdapter, 'getBuildingByCode')
52+
.mockResolvedValueOnce({ ok: false, err: 'not-found' })
53+
54+
const res = await request(app.callback()).get(
55+
'/propertyBase/buildings/by-building-code/123-456'
56+
)
57+
58+
expect(res.status).toBe(404)
59+
expect(getBuildingSpy).toHaveBeenCalledWith('123-456')
60+
})
61+
62+
it('returns 500 if an error occurs', async () => {
63+
const getBuildingSpy = jest
64+
.spyOn(propertyBaseAdapter, 'getBuildingByCode')
65+
.mockResolvedValueOnce({ ok: false, err: 'unknown' })
66+
67+
const res = await request(app.callback()).get(
68+
'/propertyBase/buildings/by-building-code/123-456'
69+
)
70+
71+
expect(res.status).toBe(500)
72+
expect(getBuildingSpy).toHaveBeenCalledWith('123-456')
73+
})
74+
})
75+
3176
describe('GET /propertyBase/companies', () => {
3277
it('returns 200 and a list of companies', async () => {
3378
const companiesMock = factory.company.buildList(3)

src/services/search-service/schemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const PropertySearchResultSchema = z.object({
99
export const BuildingSearchResultSchema = z.object({
1010
id: z.string().describe('Unique identifier for the search result'),
1111
type: z.literal('building').describe('Indicates this is a building result'),
12-
name: z.string().describe('Name of the building'),
12+
name: z.string().nullable().describe('Name of the building'),
1313
property: z
1414
.object({
1515
name: z

0 commit comments

Comments
 (0)