Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ENOENT: no such file or directory #910

Merged
merged 13 commits into from
Aug 7, 2023
6 changes: 3 additions & 3 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/src/main.js",
"lint": "eslint \"{src,apps,libs,test,scripts}/**/*.{ts,js}\" --max-warnings 0",
"test": "cross-env NODE_ENV=test jest --config ./test/jest.json --silent",
"test": "cross-env NODE_ENV=test jest --config ./test/jest.json",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
Expand Down Expand Up @@ -121,7 +121,7 @@
"@types/passport": "^1.0.4",
"@types/passport-strategy": "^0.2.35",
"@types/sharp": "^0.30.4",
"@types/supertest": "^2.0.8",
"@types/supertest": "2.0.12",
"@types/ungap__structured-clone": "^0.3.0",
"@ungap/structured-clone": "^1.2.0",
"faker": "^4.1.0",
Expand All @@ -131,7 +131,7 @@
"jest": "26.6.0",
"nodemon": "^2.0.4",
"rimraf": "^3.0.2",
"supertest": "^4.0.2",
"supertest": "6.3.3",
"ts-jest": "^24.0",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
Expand Down
231 changes: 103 additions & 128 deletions packages/api/src/time-series/time-series.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
import { User } from '../users/users.entity';
import { Site } from '../sites/sites.entity';
import { SiteSurveyPoint } from '../site-survey-points/site-survey-points.entity';
import { TimeSeries } from './time-series.entity';

// https://github.com/jsdom/jsdom/issues/3363
global.structuredClone = structuredClone.default as any;
Expand Down Expand Up @@ -180,156 +179,132 @@ export const timeSeriesTests = () => {
expect(rsp.text).toMatch(expectedData);
});

it('POST upload uploads data', async () => {
const user = await dataSource.getRepository(User).findOne({
where: { firebaseUid: siteManagerUserMock.firebaseUid as string },
select: ['id'],
});
describe('POST /upload uploads data', () => {
let firstSiteRows: number;
let user: User | null;
let sites: Site[];
let surveyPoints: SiteSurveyPoint[];
let fistSitePointId: number;

it('setups the user relations', async () => {
user = await dataSource.getRepository(User).findOne({
where: { firebaseUid: siteManagerUserMock.firebaseUid as string },
select: ['id'],
});

const sites = await dataSource.getRepository(Site).find();
expect(sites.length).toBe(3);
sites = await dataSource.getRepository(Site).find();
expect(sites.length).toBe(3);

const surveyPoints = await dataSource.getRepository(SiteSurveyPoint).find();
const fistSitePointId = surveyPoints.find((x) => x.siteId === sites[0].id)
?.id as number;
surveyPoints = await dataSource.getRepository(SiteSurveyPoint).find();
fistSitePointId = surveyPoints.find((x) => x.siteId === sites[0].id)
?.id as number;

expect(fistSitePointId).toBeDefined();
expect(csvDataMock.length).toBe(30);
expect(fistSitePointId).toBeDefined();
expect(csvDataMock.length).toBe(30);

await dataSource
.getRepository(User)
.createQueryBuilder('user')
.relation('administeredSites')
.of(user)
.add(sites.slice(0, 2));
await dataSource
.getRepository(User)
.createQueryBuilder('user')
.relation('administeredSites')
.of(user)
.add(sites.slice(0, 2));

const firstSiteRows = 20;
firstSiteRows = 20;
});

const editedData = csvDataMock.map((row, i) => {
const result = row;
// TODO: find out why this test fails
// eslint-disable-next-line no-unused-expressions
false &&
it('completes the request with correct data', async () => {
const editedData = csvDataMock.map((row, i) => {
const result = row;

if (i < firstSiteRows) result['aqualink_site_id'] = sites[0].id;
else result['aqualink_site_id'] = sites[1].id;
if (i < firstSiteRows) result['aqualink_site_id'] = sites[0].id;
else result['aqualink_site_id'] = sites[1].id;

if (i < firstSiteRows)
result['aqualink_survey_point_id'] = fistSitePointId;
else result['aqualink_survey_point_id'] = '';
if (i < firstSiteRows)
result['aqualink_survey_point_id'] = fistSitePointId;
else result['aqualink_survey_point_id'] = '';

if (i < firstSiteRows - 10)
result['aqualink_sensor_type'] = SourceType.HUI;
else result['aqualink_sensor_type'] = SourceType.SONDE;
if (i < firstSiteRows - 10)
result['aqualink_sensor_type'] = SourceType.HUI;
else result['aqualink_sensor_type'] = SourceType.SONDE;

return result;
});
return result;
});

const csvString = stringify(editedData, { header: true });

mockExtractAndVerifyToken(siteManager2FirebaseUserMock);
const response = await request(app.getHttpServer())
.post('/time-series/upload?failOnWarning=false')
.attach('files', Buffer.from(csvString), 'data.csv')
.set('Content-Type', 'text/csv');

const result1 = await dataSource
.getRepository(TimeSeries)
.createQueryBuilder('ts')
.select('count(*)', 'count')
.innerJoin(
'ts.source',
'source',
'source.site_id = :siteId AND source.survey_point_id = :surveyPointId',
{ siteId: sites[0].id, surveyPointId: fistSitePointId },
)
.leftJoin('source.surveyPoint', 'surveyPoint')
.andWhere('timestamp >= :startDate', {
startDate: '2023/01/01 00:00:00.000',
})
.andWhere('timestamp <= :endDate', {
endDate: '2023/01/01 23:59:59.999',
})
.getRawOne();

const result2 = await dataSource
.getRepository(TimeSeries)
.createQueryBuilder('ts')
.select('count(*)', 'count')
.innerJoin(
'ts.source',
'source',
'source.site_id = :siteId AND source.survey_point_id is NULL',
{ siteId: sites[1].id },
)
.leftJoin('source.surveyPoint', 'surveyPoint')
.andWhere('timestamp >= :startDate', {
startDate: '2023/01/01 00:00:00.000',
})
.andWhere('timestamp <= :endDate', {
endDate: '2023/01/01 23:59:59.999',
})
.getRawOne();

const result3 = await dataSource
.getRepository(TimeSeries)
.createQueryBuilder('ts')
.select('count(*)', 'count')
.innerJoin(
'ts.source',
'source',
`source.site_id = :siteId AND source.survey_point_id = :surveyPointId AND source.type = 'hui'`,
{ siteId: sites[0].id, surveyPointId: fistSitePointId },
)
.leftJoin('source.surveyPoint', 'surveyPoint')
.andWhere('timestamp >= :startDate', {
startDate: '2023/01/01 00:00:00.000',
})
.andWhere('timestamp <= :endDate', {
endDate: '2023/01/01 23:59:59.999',
})
.getRawOne();

// we have 3 data columns
expect(Number(result1.count)).toBe(firstSiteRows * 3);

expect(Number(result2.count)).toBe(
(csvDataMock.length - firstSiteRows) * 3,
);
const csvString = stringify(editedData, { header: true });

expect(Number(result3.count)).toBe((firstSiteRows - 10) * 3);
mockExtractAndVerifyToken(siteManager2FirebaseUserMock);
const resp = await request(app.getHttpServer())
.post('/time-series/upload?failOnWarning=false')
.attach('files', Buffer.from(csvString), 'data.csv')
.set('Content-Type', 'text/csv');

expect(response.status).toBe(201);
});
expect(resp.status).toBe(201);

it('POST upload fails for wrong site id', async () => {
const sites = await dataSource.getRepository(Site).find();
expect(sites.length).toBe(3);
expect(
resp.body.find((x) => x.error !== undefined && x.error !== null),
).toBeUndefined();
});

expect(csvDataMock.length).toBe(30);
it('upload fails for wrong site id', async () => {
const editedData = csvDataMock.map((row, i) => {
const result = row;

const firstSiteRows = 20;
// 2 is the invalid site ID here, since the user is admin only to sites 0 and 1
if (i < firstSiteRows) result['aqualink_site_id'] = sites[0].id;
else result['aqualink_site_id'] = sites[2].id;

const editedData = csvDataMock.map((row, i) => {
const result = row;
return result;
});

const csvString = stringify(editedData, { header: true });

// 2 is the invalid site ID here, since the user is admin only to sites 0 and 1
if (i < firstSiteRows) result['aqualink_site_id'] = sites[0].id;
else result['aqualink_site_id'] = sites[2].id;
mockExtractAndVerifyToken(siteManager2FirebaseUserMock);
const response = await request(app.getHttpServer())
.post('/time-series/upload?failOnWarning=false')
.attach('files', Buffer.from(csvString), 'data2.csv')
.set('Content-Type', 'text/csv');

return result;
expect(
response.body.find(
(x) => x.error === `Invalid values for 'aqualink_site_id'`,
),
).toBeDefined();
});

const csvString = stringify(editedData, { header: true });
it('upload fails for wrong survey point id', async () => {
const wrongPointId = surveyPoints.find((x) => x.id !== fistSitePointId)
?.id as number;

const editedData = csvDataMock.map((row, i) => {
const result = row;

mockExtractAndVerifyToken(siteManager2FirebaseUserMock);
const response = await request(app.getHttpServer())
.post('/time-series/upload?failOnWarning=false')
.attach('files', Buffer.from(csvString), 'data.csv')
.set('Content-Type', 'text/csv');
if (i < firstSiteRows) result['aqualink_site_id'] = sites[0].id;
else result['aqualink_site_id'] = sites[1].id;

expect(
response.body.find(
(x) => x.error === `Invalid values for 'aqualink_site_id'`,
),
).toBeDefined();
if (i < firstSiteRows)
result['aqualink_survey_point_id'] = wrongPointId;
else result['aqualink_survey_point_id'] = '';

return result;
});

const csvString = stringify(editedData, { header: true });

mockExtractAndVerifyToken(siteManager2FirebaseUserMock);
const response = await request(app.getHttpServer())
.post('/time-series/upload?failOnWarning=false')
.attach('files', Buffer.from(csvString), 'data2.csv')
.set('Content-Type', 'text/csv');

expect(
response.body.find((x) =>
(x.error as string).startsWith('Survey point with id'),
),
).toBeDefined();
});
});

it('GET sites/:siteId/csv fetch data as csv', async () => {
Expand Down
8 changes: 2 additions & 6 deletions packages/api/src/utils/uploads/upload-sheet-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,10 @@ const rules: Rule[] = [

export type Mimetype = typeof ACCEPTED_FILE_TYPES[number]['mimetype'];

export const fileFilter: MulterOptions['fileFilter'] = (
_,
{ mimetype: inputMimetype },
callback,
) => {
export const fileFilter: MulterOptions['fileFilter'] = (_, file, callback) => {
if (
!ACCEPTED_FILE_TYPES.map(({ mimetype }) => mimetype as string).includes(
inputMimetype,
file.mimetype,
)
) {
callback(
Expand Down
2 changes: 1 addition & 1 deletion packages/api/test/test.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class TestService {

this.app = moduleFixture.createNestApplication();

this.app = await this.app.init();
await this.app.init();

this.app.useGlobalPipes(
new GlobalValidationPipe({
Expand Down
Loading
Loading