diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml
index 096caeb5..37d240df 100644
--- a/.github/workflows/api.yml
+++ b/.github/workflows/api.yml
@@ -34,3 +34,7 @@ jobs:
- run: npm install
- run: npm run lint
- run: npm run build
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/api/coverage/cobertura-coverage.xml b/api/coverage/cobertura-coverage.xml
new file mode 100644
index 00000000..b4cff3a6
--- /dev/null
+++ b/api/coverage/cobertura-coverage.xml
@@ -0,0 +1,2404 @@
+
+
+
+
+ C:\dev\movinin\src\api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/jest.config.js b/api/jest.config.js
index 85cc2814..b3081946 100644
--- a/api/jest.config.js
+++ b/api/jest.config.js
@@ -7,6 +7,8 @@ const config = {
roots: [
'./tests/',
],
+ collectCoverage: true,
+ coverageReporters: ['cobertura'],
}
export default config
diff --git a/api/package.json b/api/package.json
index 24bca182..d9b08e1f 100644
--- a/api/package.json
+++ b/api/package.json
@@ -8,7 +8,7 @@
"dev": "nodemon",
"build": "rimraf dist && tsc && babel dist -d dist",
"start": "npm run build && node dist/src",
- "test": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest",
+ "test": "rimraf coverage && cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"lint": "eslint --ext .ts .",
"ncu": "ncu -u"
},
diff --git a/api/src/controllers/locationController.ts b/api/src/controllers/locationController.ts
index a086b4fe..76226380 100644
--- a/api/src/controllers/locationController.ts
+++ b/api/src/controllers/locationController.ts
@@ -62,7 +62,7 @@ export async function create(req: Request, res: Response) {
const location = new Location({ values })
await location.save()
- return res.sendStatus(200)
+ return res.json(location)
} catch (err) {
console.error(`[location.create] ${strings.DB_ERROR} ${req.body}`, err)
return res.status(400).send(strings.DB_ERROR + err)
@@ -102,7 +102,7 @@ export async function update(req: Request, res: Response) {
await location.save()
}
}
- return res.sendStatus(200)
+ return res.json(location)
}
console.error('[location.update] Location not found:', id)
diff --git a/api/tests/agency.test.ts b/api/tests/agency.test.ts
index c5b29db9..26b15d2e 100644
--- a/api/tests/agency.test.ts
+++ b/api/tests/agency.test.ts
@@ -1,11 +1,19 @@
import 'dotenv/config'
import request from 'supertest'
+import url from 'url'
+import path from 'path'
+import fs from 'node:fs/promises'
import * as movininTypes from 'movinin-types'
import * as DatabaseHelper from '../src/common/DatabaseHelper'
import * as TestHelper from './TestHelper'
import app from '../src/app'
import * as env from '../src/config/env.config'
+import * as Helper from '../src/common/Helper'
import User from '../src/models/User'
+import Property from '../src/models/Property'
+
+const __filename = url.fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
let AGENCY1_ID: string
let AGENCY2_ID: string
@@ -149,20 +157,69 @@ describe('DELETE /api/delete-agency/:id', () => {
const token = await TestHelper.signinAsAdmin()
const agencyName = TestHelper.getAgencyName()
- const _id = await TestHelper.createAgency(`${agencyName}@test.movinin.io`, agencyName)
+ const agencyId = await TestHelper.createAgency(`${agencyName}@test.movinin.io`, agencyName)
- let agency = await User.findById(_id)
+ let agency = await User.findById(agencyId)
expect(agency).not.toBeNull()
+ const avatarName = 'avatar1.jpg'
+ const avatarPath = path.resolve(__dirname, `./img/${avatarName}`)
+
+ const avatar = path.join(env.CDN_USERS, avatarName)
+ if (!await Helper.exists(avatar)) {
+ fs.copyFile(avatarPath, avatar)
+ }
+ agency!.avatar = avatarName
+ await agency?.save()
+
+ const locationId = await TestHelper.createLocation('Location 1 EN', 'Location 1 FR')
+
+ const propertyImageName = 'main1.jpg'
+ const propertyImagePath = path.resolve(__dirname, `./img/${propertyImageName}`)
+
+ const property = new Property({
+ name: 'Beautiful House in Detroit',
+ agency: agencyId,
+ type: movininTypes.PropertyType.House,
+ description: '
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium rem aperiam, veritatis et quasi.
',
+ image: propertyImageName,
+ images: [],
+ bedrooms: 3,
+ bathrooms: 2,
+ kitchens: 1,
+ parkingSpaces: 1,
+ size: 200,
+ petsAllowed: false,
+ furnished: true,
+ aircon: true,
+ minimumAge: 21,
+ location: locationId,
+ address: '',
+ price: 4000,
+ hidden: true,
+ cancellation: 0,
+ available: false,
+ rentalTerm: movininTypes.RentalTerm.Monthly,
+ })
+
+ const propertyImage = path.join(env.CDN_PROPERTIES, propertyImageName)
+ if (!await Helper.exists(propertyImage)) {
+ fs.copyFile(propertyImagePath, propertyImage)
+ }
+
+ await property.save()
+
const res = await request(app)
- .delete(`/api/delete-agency/${_id}`)
+ .delete(`/api/delete-agency/${agencyId}`)
.set(env.X_ACCESS_TOKEN, token)
expect(res.statusCode).toBe(200)
- agency = await User.findById(_id)
+ agency = await User.findById(agencyId)
expect(agency).toBeNull()
+ await TestHelper.deleteLocation(locationId)
+
await TestHelper.signout(token)
})
})
diff --git a/api/tests/location.test.ts b/api/tests/location.test.ts
index a53b3e9d..a3376296 100644
--- a/api/tests/location.test.ts
+++ b/api/tests/location.test.ts
@@ -1,6 +1,27 @@
import 'dotenv/config'
+import request from 'supertest'
+import { v1 as uuid } from 'uuid'
+import * as movininTypes from 'movinin-types'
+import app from '../src/app'
import * as DatabaseHelper from '../src/common/DatabaseHelper'
import * as TestHelper from './TestHelper'
+import * as env from '../src/config/env.config'
+import LocationValue from '../src/models/LocationValue'
+import Location from '../src/models/Location'
+import Property from '../src/models/Property'
+
+let LOCATION_ID: string
+
+let LOCATION_NAMES: movininTypes.LocationName[] = [
+ {
+ language: 'en',
+ name: uuid(),
+ },
+ {
+ language: 'fr',
+ name: uuid(),
+ },
+]
//
// Connecting and initializing the database before running the test suite
@@ -27,7 +48,34 @@ describe('POST /api/validate-location', () => {
it('should validate a location', async () => {
const token = await TestHelper.signinAsAdmin()
- // TODO
+ const language = TestHelper.LANGUAGE
+ const name = uuid()
+
+ const locationValue = new LocationValue({ language, value: name })
+ await locationValue.save()
+
+ const payload: movininTypes.ValidateLocationPayload = {
+ language,
+ name,
+ }
+
+ let res = await request(app)
+ .post('/api/validate-location')
+ .set(env.X_ACCESS_TOKEN, token)
+ .send(payload)
+
+ expect(res.statusCode).toBe(204)
+
+ payload.name = uuid()
+
+ res = await request(app)
+ .post('/api/validate-location')
+ .set(env.X_ACCESS_TOKEN, token)
+ .send(payload)
+
+ expect(res.statusCode).toBe(200)
+
+ await LocationValue.deleteOne({ _id: locationValue._id })
await TestHelper.signout(token)
})
@@ -37,17 +85,47 @@ describe('POST /api/create-location', () => {
it('should create a location', async () => {
const token = await TestHelper.signinAsAdmin()
- // TODO
+ const payload: movininTypes.LocationName[] = LOCATION_NAMES
+
+ const res = await request(app)
+ .post('/api/create-location')
+ .set(env.X_ACCESS_TOKEN, token)
+ .send(payload)
+
+ expect(res.statusCode).toBe(200)
+ expect(res.body?.values?.length).toBe(2)
+ LOCATION_ID = res.body?._id
await TestHelper.signout(token)
})
})
-describe('POST /api/update-location/:id', () => {
+describe('PUT /api/update-location/:id', () => {
it('should update a location', async () => {
const token = await TestHelper.signinAsAdmin()
- // TODO
+ LOCATION_NAMES = [
+ {
+ language: 'en',
+ name: uuid(),
+ },
+ {
+ language: 'fr',
+ name: uuid(),
+ },
+ {
+ language: 'es',
+ name: uuid(),
+ },
+ ]
+
+ const res = await request(app)
+ .put(`/api/update-location/${LOCATION_ID}`)
+ .set(env.X_ACCESS_TOKEN, token)
+ .send(LOCATION_NAMES)
+
+ expect(res.statusCode).toBe(200)
+ expect(res.body.values?.length).toBe(3)
await TestHelper.signout(token)
})
@@ -55,17 +133,25 @@ describe('POST /api/update-location/:id', () => {
describe('GET /api/location/:id/:language', () => {
it('should get a location', async () => {
+ const language = 'en'
- // TODO
+ const res = await request(app)
+ .get(`/api/location/${LOCATION_ID}/${language}`)
+ expect(res.statusCode).toBe(200)
+ expect(res.body?.name).toBe(LOCATION_NAMES.filter((v) => v.language === language)[0].name)
})
})
describe('GET /api/locations/:page/:size/:language', () => {
it('should get locations', async () => {
+ const language = 'en'
- // TODO
+ const res = await request(app)
+ .get(`/api/locations/${TestHelper.PAGE}/${TestHelper.SIZE}/${language}?s=${LOCATION_NAMES[0].name}`)
+ expect(res.statusCode).toBe(200)
+ expect(res.body.length).toBe(1)
})
})
@@ -73,7 +159,49 @@ describe('GET /api/check-location/:id', () => {
it('should check a location', async () => {
const token = await TestHelper.signinAsAdmin()
- // TODO
+ const agencyName = TestHelper.getAgencyName()
+ const agencyId = await TestHelper.createAgency(`${agencyName}@test.movinin.io`, agencyName)
+
+ const property = new Property({
+ name: 'Beautiful House in Detroit',
+ agency: agencyId,
+ type: movininTypes.PropertyType.House,
+ description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium rem aperiam, veritatis et quasi.
',
+ image: 'main1.jpg',
+ images: [],
+ bedrooms: 3,
+ bathrooms: 2,
+ kitchens: 1,
+ parkingSpaces: 1,
+ size: 200,
+ petsAllowed: false,
+ furnished: true,
+ aircon: true,
+ minimumAge: 21,
+ location: LOCATION_ID,
+ address: '',
+ price: 4000,
+ hidden: true,
+ cancellation: 0,
+ available: false,
+ rentalTerm: movininTypes.RentalTerm.Monthly,
+ })
+ await property.save()
+
+ let res = await request(app)
+ .get(`/api/check-location/${LOCATION_ID}`)
+ .set(env.X_ACCESS_TOKEN, token)
+
+ expect(res.statusCode).toBe(200)
+
+ await Property.deleteOne({ _id: property._id })
+ await TestHelper.deleteAgency(agencyId)
+
+ res = await request(app)
+ .get(`/api/check-location/${LOCATION_ID}`)
+ .set(env.X_ACCESS_TOKEN, token)
+
+ expect(res.statusCode).toBe(204)
await TestHelper.signout(token)
})
@@ -83,7 +211,17 @@ describe('DELETE /api/delete-location/:id', () => {
it('should delete a location', async () => {
const token = await TestHelper.signinAsAdmin()
- // TODO
+ let location = await Location.findById(LOCATION_ID)
+ expect(location).not.toBeNull()
+
+ const res = await request(app)
+ .delete(`/api/delete-location/${LOCATION_ID}`)
+ .set(env.X_ACCESS_TOKEN, token)
+
+ expect(res.statusCode).toBe(200)
+
+ location = await Location.findById(LOCATION_ID)
+ expect(location).toBeNull()
await TestHelper.signout(token)
})