diff --git a/packages/origin-backend/.env.test b/packages/origin-backend/.env.test index 6e3fc8af95..7a715c3444 100644 --- a/packages/origin-backend/.env.test +++ b/packages/origin-backend/.env.test @@ -1 +1 @@ -PORT=3030 \ No newline at end of file +BACKEND_PORT=3030 \ No newline at end of file diff --git a/packages/origin-backend/package.json b/packages/origin-backend/package.json index 47da3cf642..960a0abd2b 100644 --- a/packages/origin-backend/package.json +++ b/packages/origin-backend/package.json @@ -41,7 +41,9 @@ "@nestjs/jwt": "6.1.1", "@nestjs/passport": "6.1.1", "@nestjs/platform-express": "^6.10.14", + "@nestjs/platform-ws": "6.10.4", "@nestjs/typeorm": "^6.2.0", + "@nestjs/websockets": "6.10.4", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "class-transformer": "0.2.3", @@ -57,7 +59,8 @@ "reflect-metadata": "0.1.13", "rxjs": "6.5.4", "sqlite3": "4.1.1", - "typeorm": "0.2.22" + "typeorm": "0.2.22", + "ws": "7.2.1" }, "devDependencies": { "@nestjs/cli": "6.14.1", @@ -74,6 +77,8 @@ "@types/passport-jwt": "3.0.3", "@types/passport-local": "1.0.33", "@types/supertest": "2.0.8", + "@types/websocket": "1.0.0", + "@types/ws": "^7.2.0", "axios": "0.19.2", "jest": "25.1.0", "supertest": "4.0.2", diff --git a/packages/origin-backend/src/app.module.ts b/packages/origin-backend/src/app.module.ts index 1bd81f8e3f..3ed3d6b9bf 100644 --- a/packages/origin-backend/src/app.module.ts +++ b/packages/origin-backend/src/app.module.ts @@ -11,6 +11,9 @@ import { Currency } from './pods/currency/currency.entity'; import { Compliance } from './pods/compliance/compliance.entity'; import { Organization } from './pods/organization/organization.entity'; import { User } from './pods/user/user.entity'; +import { Device } from './pods/device/device.entity'; +import { Demand } from './pods/demand/demand.entity'; + import { UserModule } from './pods/user/user.module'; import { ComplianceModule } from './pods/compliance/compliance.module'; import createConfig from './config/configuration'; @@ -21,7 +24,11 @@ import { ImageModule } from './pods/image/image.module'; import { JsonEntityModule } from './pods/json-entity/json-entity.module'; import { ContractsStorageModule } from './pods/contracts-storage/contracts-storage.module'; import { OrganizationModule } from './pods/organization/organization.module'; +import { DeviceModule } from './pods/device/device.module'; +import { DemandModule } from './pods/demand/demand.module'; import { AuthModule } from './auth/auth.module'; +import { EventsModule } from './events/events.module'; + import { AppController } from './app.controller'; import { OrganizationInvitation } from './pods/organization/organizationInvitation.entity'; @@ -43,6 +50,8 @@ const ENV_FILE_PATH = path.resolve(__dirname, '../../../../../.env'); Currency, Compliance, Country, + Device, + Demand, Organization, User, OrganizationInvitation @@ -58,7 +67,10 @@ const ENV_FILE_PATH = path.resolve(__dirname, '../../../../../.env'); JsonEntityModule, ContractsStorageModule, OrganizationModule, - AuthModule + DeviceModule, + DemandModule, + AuthModule, + EventsModule ], controllers: [AppController], providers: [] diff --git a/packages/origin-backend/src/events/events.gateway.ts b/packages/origin-backend/src/events/events.gateway.ts new file mode 100644 index 0000000000..7ac0b46fce --- /dev/null +++ b/packages/origin-backend/src/events/events.gateway.ts @@ -0,0 +1,89 @@ +import { + SubscribeMessage, + WebSocketGateway, + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + MessageBody +} from '@nestjs/websockets'; +import { Logger } from '@nestjs/common'; +import { getEventsServerPort } from '../port' + +import moment from 'moment'; +import { SupportedEvents, IEvent, NewEvent } from '@energyweb/origin-backend-core'; + +const PORT = getEventsServerPort(); + +@WebSocketGateway(PORT, { transports: ['websocket'] }) +export class EventsWebSocketGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { + + private logger: Logger = new Logger('EventsWebSocketGateway'); + private allEvents: IEvent[] = []; + + wsClients: any[] = []; + + afterInit() { + this.logger.log(`Initialized the WebSockets server on port: ${PORT}.`); + } + + handleConnection(client: any) { + this.wsClients.push(client);; + + this.logger.log(`Client connected. Total clients connected: ${this.wsClients.length}`); + } + + handleDisconnect(client: any) { + for (let i = 0; i < this.wsClients.length; i++) { + if (this.wsClients[i] === client) { + this.wsClients.splice(i, 1); + this.logger.log(`Client disconnected`); + break; + } + } + } + + private broadcastEvent(event: IEvent) { + this.logger.log(`Broadcasting a new "${event.type}" event.`); + + const content = JSON.stringify(event); + + for (let client of this.wsClients) { + client.send(content); + } + } + + @SubscribeMessage('getAllEvents') + getAllEvents(client: any) { + this.logger.log('Client requested getting all events.'); + + client.send(JSON.stringify(this.allEvents)); + } + + @SubscribeMessage('events') + handleEvent(@MessageBody() incomingEvent: NewEvent) { + this.logger.log(`Incoming event: ${JSON.stringify(incomingEvent)}`); + + const { type, data } = incomingEvent; + + if (!type || !data) { + return 'Incorrect event structure'; + } + + const supportedEvents = Object.values(SupportedEvents); + + if (!supportedEvents.includes(type)) { + return `Unsupported event name. Please use one of the following: ${supportedEvents.join(', ')}`; + } + + const event: IEvent = { + ...incomingEvent, + timestamp: moment().unix() + }; + + this.allEvents.push(event); + + this.broadcastEvent(event); + + return `Saved ${type} event.`; + } +} \ No newline at end of file diff --git a/packages/origin-backend/src/events/events.module.ts b/packages/origin-backend/src/events/events.module.ts new file mode 100644 index 0000000000..9b2d365f85 --- /dev/null +++ b/packages/origin-backend/src/events/events.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { EventsWebSocketGateway } from './events.gateway'; + +@Module({ + providers: [EventsWebSocketGateway], + exports: [EventsWebSocketGateway] +}) +export class EventsModule {} diff --git a/packages/origin-backend/src/index.ts b/packages/origin-backend/src/index.ts index 8e03f3e58b..fa308d2c46 100644 --- a/packages/origin-backend/src/index.ts +++ b/packages/origin-backend/src/index.ts @@ -1,26 +1,17 @@ import { NestFactory } from '@nestjs/core'; import { LoggerService } from '@nestjs/common'; +import { WsAdapter } from '@nestjs/platform-ws'; import { AppModule } from './app.module'; - -function extractPort(url: string): number { - if (url) { - const backendUrlSplit: string[] = url.split(':'); - const extractedPort: number = parseInt(backendUrlSplit[backendUrlSplit.length - 1], 10); - - return extractedPort; - } - - return null; -} +import { getPort } from './port' export async function startAPI(logger?: LoggerService) { - const PORT: number = - parseInt(process.env.PORT, 10) || extractPort(process.env.BACKEND_URL) || 3030; - + const PORT = getPort(); + console.log(`Backend starting on port: ${PORT}`); const app = await NestFactory.create(AppModule); + app.useWebSocketAdapter(new WsAdapter(app)); app.enableCors(); app.setGlobalPrefix('api'); @@ -31,4 +22,4 @@ export async function startAPI(logger?: LoggerService) { await app.listen(PORT); return app; -} +} \ No newline at end of file diff --git a/packages/origin-backend/src/pods/demand/demand.controller.ts b/packages/origin-backend/src/pods/demand/demand.controller.ts new file mode 100644 index 0000000000..7e5d4e2b51 --- /dev/null +++ b/packages/origin-backend/src/pods/demand/demand.controller.ts @@ -0,0 +1,182 @@ +import { Repository } from 'typeorm'; +import { validate } from 'class-validator'; +import { IDemand, DemandStatus, DemandPostData, DemandUpdateData, SupportedEvents, CreatedNewDemand, DemandPartiallyFilledEvent } from '@energyweb/origin-backend-core'; + +import { + Controller, + Get, + Param, + NotFoundException, + Post, + Body, + UnprocessableEntityException, + Delete, + Put, + Inject +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Demand } from './demand.entity'; +import { StorageErrors } from '../../enums/StorageErrors'; +import { EventsWebSocketGateway } from '../../events/events.gateway'; + +@Controller('/Demand') +export class DemandController { + constructor( + @InjectRepository(Demand) private readonly demandRepository: Repository, + @Inject(EventsWebSocketGateway) private readonly eventGateway: EventsWebSocketGateway + ) {} + + @Get() + async getAll() { + console.log(` Demand all`); + + const allDemands = await this.demandRepository.find(); + + for (let demand of allDemands) { + demand.demandPartiallyFilledEvents = demand.demandPartiallyFilledEvents.map( + event => JSON.parse(event) + ); + } + + return allDemands; + } + + @Get('/:id') + async get(@Param('id') id: string) { + const existing = await this.demandRepository.findOne(id, { + loadRelationIds: true + }); + + if (!existing) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + existing.demandPartiallyFilledEvents = existing.demandPartiallyFilledEvents.map( + event => JSON.parse(event) + ); + + return existing; + } + + @Post() + async post(@Body() body: DemandPostData) { + let newEntity = new Demand(); + + const data: Omit = { + ...body, + status: DemandStatus.ACTIVE, + demandPartiallyFilledEvents: [], + location: body.location ?? [], + deviceType: body.deviceType ?? [], + otherGreenAttributes: body.otherGreenAttributes ?? '', + typeOfPublicSupport: body.typeOfPublicSupport ?? '', + registryCompliance: body.registryCompliance ?? '', + procureFromSingleFacility: body.procureFromSingleFacility ?? false, + vintage: body.vintage ?? [1900, 2100] + }; + + Object.assign(newEntity, data); + + const validationErrors = await validate(newEntity); + + if (validationErrors.length > 0) { + throw new UnprocessableEntityException({ + success: false, + errors: validationErrors + }); + } + + newEntity = await this.demandRepository.save(newEntity); + + const eventData: CreatedNewDemand = { + demandId: newEntity.id + }; + + this.eventGateway.handleEvent({ + type: SupportedEvents.CREATE_NEW_DEMAND, + data: eventData + }); + + return newEntity; + } + + @Delete('/:id') + async delete(@Param('id') id: string) { + const existing = await this.demandRepository.findOne(id); + + if (!existing) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + existing.status = DemandStatus.ARCHIVED; + + try { + await existing.save(); + + return { + message: `Demand ${id} successfully archived` + }; + } catch (error) { + throw new UnprocessableEntityException({ + message: `Demand ${id} could not be archived due to an unknown error` + }); + } + } + + @Put('/:id') + async put(@Param('id') id: string, @Body() body: DemandUpdateData) { + const existing = await this.demandRepository.findOne(id); + + if (!existing) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + existing.status = body.status ?? existing.status; + + if (body.demandPartiallyFilledEvent) { + existing.demandPartiallyFilledEvents.push( + JSON.stringify(body.demandPartiallyFilledEvent) + ); + } + + const hasNewFillEvent = body.demandPartiallyFilledEvent !== null; + + if (hasNewFillEvent) { + existing.demandPartiallyFilledEvents.push( + JSON.stringify(body.demandPartiallyFilledEvent) + ); + } + + try { + await existing.save(); + } catch (error) { + throw new UnprocessableEntityException({ + message: `Demand ${id} could not be updated due to an unkown error` + }); + } + + this.eventGateway.handleEvent({ + type: SupportedEvents.DEMAND_UPDATED, + data: { demandId: existing.id } + }); + + if (hasNewFillEvent) { + const eventData: DemandPartiallyFilledEvent = { + demandId: existing.id, + certificateId: body.demandPartiallyFilledEvent.certificateId, + energy: body.demandPartiallyFilledEvent.energy, + blockNumber: body.demandPartiallyFilledEvent.blockNumber + }; + + this.eventGateway.handleEvent({ + type: SupportedEvents.DEMAND_PARTIALLY_FILLED, + data: eventData + }); + } + + return { + message: `Demand ${id} successfully updated` + }; + } +} diff --git a/packages/origin-backend/src/pods/demand/demand.entity.ts b/packages/origin-backend/src/pods/demand/demand.entity.ts new file mode 100644 index 0000000000..495b442626 --- /dev/null +++ b/packages/origin-backend/src/pods/demand/demand.entity.ts @@ -0,0 +1,68 @@ +import { Entity, Column, BaseEntity, PrimaryGeneratedColumn } from 'typeorm'; +import { IsInt, Min, IsBoolean, IsOptional } from 'class-validator'; +import { DemandStatus, IDemandProperties } from '@energyweb/origin-backend-core'; + +@Entity() +export class Demand extends BaseEntity implements IDemandProperties { + @PrimaryGeneratedColumn() + id: number; + + @Column() + owner: string; + + @Column() + @IsInt() + status: DemandStatus; + + @Column() + startTime: number; + + @Column() + endTime: number; + + @Column() + @IsInt() + @Min(0) + timeFrame: number; + + @Column() + @Min(0) + maxPriceInCentsPerMwh: number; + + @Column() + currency: string; + + @Column() + @Min(0) + energyPerTimeFrame: number; + + @Column() + @IsBoolean() + automaticMatching: boolean; + + @Column('simple-array') + location?: string[]; + + @Column('simple-array') + deviceType?: string[]; + + @Column() + otherGreenAttributes?: string; + + @Column() + typeOfPublicSupport?: string; + + @Column() + registryCompliance?: string; + + @Column({ default: false }) + @IsOptional() + @IsBoolean() + procureFromSingleFacility?: boolean; + + @Column('simple-array') + vintage?: [number, number]; + + @Column('simple-array') + demandPartiallyFilledEvents: string[]; +} diff --git a/packages/origin-backend/src/pods/demand/demand.module.ts b/packages/origin-backend/src/pods/demand/demand.module.ts new file mode 100644 index 0000000000..d16d09923e --- /dev/null +++ b/packages/origin-backend/src/pods/demand/demand.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Demand } from './demand.entity'; +import { DemandController } from './demand.controller'; +import { EventsModule } from '../../events/events.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([Demand]), EventsModule], + providers: [], + controllers: [DemandController] +}) +export class DemandModule {} diff --git a/packages/origin-backend/src/pods/device/device.controller.ts b/packages/origin-backend/src/pods/device/device.controller.ts new file mode 100644 index 0000000000..edead7169f --- /dev/null +++ b/packages/origin-backend/src/pods/device/device.controller.ts @@ -0,0 +1,132 @@ +import { Repository } from 'typeorm'; +import { validate } from 'class-validator'; +import { IDevice, DeviceStatus, DeviceUpdateData, SupportedEvents, DeviceStatusChanged } from '@energyweb/origin-backend-core'; +import { EventsWebSocketGateway } from '../../events/events.gateway'; + +import { + Controller, + Get, + Param, + NotFoundException, + Post, + Body, + BadRequestException, + UnprocessableEntityException, + Delete, + Put, + Inject +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Device } from './device.entity'; +import { StorageErrors } from '../../enums/StorageErrors'; + +@Controller('/Device') +export class DeviceController { + constructor( + @InjectRepository(Device) private readonly deviceRepository: Repository, + @Inject(EventsWebSocketGateway) private readonly eventGateway: EventsWebSocketGateway + ) {} + + @Get() + async getAll() { + console.log(` Device all`); + + return this.deviceRepository.find(); + } + + @Get('/:id') + async get(@Param('id') id: string) { + console.log(` Device/${id}`); + + const existingEntity = await this.deviceRepository.findOne(id); + + if (!existingEntity) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + return existingEntity; + } + + @Post('/:id') + async post(@Param('id') id: string, @Body() body: IDevice) { + console.log(` Device`); + + const newEntity = new Device(); + + Object.assign(newEntity, { + ...body, + status: body.status ?? DeviceStatus.Submitted + }); + + newEntity.deviceGroup = body.deviceGroup ?? ''; + newEntity.id = Number(id); + + const validationErrors = await validate(newEntity); + + if (validationErrors.length > 0) { + throw new UnprocessableEntityException({ + success: false, + errors: validationErrors + }); + } + + try { + await this.deviceRepository.save(newEntity); + + return newEntity; + } catch (error) { + console.warn('Error while saving entity', error); + throw new BadRequestException('Could not save device.'); + } + } + + @Delete('/:id') + async delete(@Param('id') id: string) { + console.log(` Device/${id}`); + const existingEntity = await this.deviceRepository.findOne(id); + + if (!existingEntity) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + await this.deviceRepository.remove(existingEntity); + + return { + message: `Entity ${id} deleted` + }; + } + + @Put('/:id') + async put(@Param('id') id: string, @Body() body: DeviceUpdateData) { + const existing = await this.deviceRepository.findOne(id); + + if (!existing) { + throw new NotFoundException(StorageErrors.NON_EXISTENT); + } + + existing.status = body.status; + + try { + await existing.save(); + + const event: DeviceStatusChanged = { + deviceId: id, + status: body.status + }; + + this.eventGateway.handleEvent({ + type: SupportedEvents.DEVICE_STATUS_CHANGED, + data: event + }); + + return { + message: `Device ${id} successfully updated` + }; + } catch (error) { + throw new UnprocessableEntityException({ + message: `Device ${id} could not be updated due to an unknown error` + }); + } + } +} diff --git a/packages/origin-backend/src/pods/device/device.entity.ts b/packages/origin-backend/src/pods/device/device.entity.ts new file mode 100644 index 0000000000..b8d2d013d7 --- /dev/null +++ b/packages/origin-backend/src/pods/device/device.entity.ts @@ -0,0 +1,70 @@ +import { Entity, Column, BaseEntity, PrimaryColumn } from 'typeorm'; +import { IsInt, IsDate, Min, IsLatitude, IsLongitude } from 'class-validator'; +import { IDevice } from '@energyweb/origin-backend-core'; + +@Entity() +export class Device extends BaseEntity implements IDevice { + @PrimaryColumn() + id: number; + + @Column() + status: number; + + @Column() + facilityName: string; + + @Column() + description: string; + + @Column() + images: string; + + @Column() + address: string; + + @Column() + region: string; + + @Column() + province: string; + + @Column() + @IsInt() + @Min(0) + country: number; + + @Column() + @Min(0) + operationalSince: number; + + @Column() + @IsInt() + @Min(0) + capacityInW: number; + + @Column() + @IsLatitude() + gpsLatitude: string; + + @Column() + @IsLongitude() + gpsLongitude: string; + + @Column() + timezone: string; + + @Column() + deviceType: string; + + @Column() + complianceRegistry: string; + + @Column() + otherGreenAttributes: string; + + @Column() + typeOfPublicSupport: string; + + @Column() + deviceGroup: string; +} diff --git a/packages/origin-backend/src/pods/device/device.module.ts b/packages/origin-backend/src/pods/device/device.module.ts new file mode 100644 index 0000000000..48d8656902 --- /dev/null +++ b/packages/origin-backend/src/pods/device/device.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Device } from './device.entity'; +import { DeviceController } from './device.controller'; +import { EventsModule } from '../../events/events.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([Device]), EventsModule], + providers: [], + controllers: [DeviceController] +}) +export class DeviceModule {} diff --git a/packages/origin-backend/src/port.ts b/packages/origin-backend/src/port.ts new file mode 100644 index 0000000000..9da9a34d05 --- /dev/null +++ b/packages/origin-backend/src/port.ts @@ -0,0 +1,18 @@ +export function extractPort(url: string): number { + if (url) { + const backendUrlSplit: string[] = url.split(':'); + const extractedPort: number = parseInt(backendUrlSplit[backendUrlSplit.length - 1], 10); + + return extractedPort; + } + + return null; +} + +export function getPort(): number { + return parseInt(process.env.BACKEND_PORT, 10) || 3030; +} + +export function getEventsServerPort(): number { + return getPort() + 1; +} \ No newline at end of file diff --git a/packages/origin-backend/src/test/e2e/Compliance.test.ts b/packages/origin-backend/src/test/e2e/Compliance.test.ts index d1e3028bd4..d556ba0e47 100644 --- a/packages/origin-backend/src/test/e2e/Compliance.test.ts +++ b/packages/origin-backend/src/test/e2e/Compliance.test.ts @@ -28,7 +28,7 @@ describe('Compliance API tests', async () => { }); let apiServer: INestApplication; - const BASE_API_URL = `http://localhost:${process.env.PORT}/api`; + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; const standard = 'I-REC'; const standard2 = 'TIGR'; diff --git a/packages/origin-backend/src/test/e2e/Country.test.ts b/packages/origin-backend/src/test/e2e/Country.test.ts index bbfbbde8f4..5c71f2d2ba 100644 --- a/packages/origin-backend/src/test/e2e/Country.test.ts +++ b/packages/origin-backend/src/test/e2e/Country.test.ts @@ -1,4 +1,4 @@ -import axios, { AxiosResponse } from 'axios'; +import axios from 'axios'; import 'mocha'; import dotenv from 'dotenv'; import { assert } from 'chai'; @@ -17,7 +17,7 @@ describe('Country API tests', async () => { }); let apiServer: INestApplication; - const BASE_API_URL = `http://localhost:${process.env.PORT}/api`; + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; const country = { name: 'Test Country', diff --git a/packages/origin-backend/src/test/e2e/Currency.test.ts b/packages/origin-backend/src/test/e2e/Currency.test.ts index c029f9b350..d96f6cb109 100644 --- a/packages/origin-backend/src/test/e2e/Currency.test.ts +++ b/packages/origin-backend/src/test/e2e/Currency.test.ts @@ -3,7 +3,6 @@ import 'mocha'; import dotenv from 'dotenv'; import { assert } from 'chai'; import * as fs from 'fs'; -import * as http from 'http'; import { INestApplication } from '@nestjs/common'; import { startAPI } from '../..'; @@ -18,7 +17,7 @@ describe('Currency API tests', async () => { }); let apiServer: INestApplication; - const BASE_API_URL = `http://localhost:${process.env.PORT}/api`; + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; const currency = 'USD'; const currency2 = 'EUR'; diff --git a/packages/origin-backend/src/test/e2e/Demand.test.ts b/packages/origin-backend/src/test/e2e/Demand.test.ts new file mode 100644 index 0000000000..b965ad9319 --- /dev/null +++ b/packages/origin-backend/src/test/e2e/Demand.test.ts @@ -0,0 +1,61 @@ +import axios from 'axios'; +import 'mocha'; +import dotenv from 'dotenv'; +import { assert } from 'chai'; +import * as fs from 'fs'; +import moment from 'moment'; +import { INestApplication } from '@nestjs/common'; +import { DemandPostData } from '@energyweb/origin-backend-core'; + +import { startAPI } from '../..'; +import { STATUS_CODES } from '../../enums/StatusCodes'; + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +describe('Demand API tests', async () => { + dotenv.config({ + path: '.env.test' + }); + let apiServer: INestApplication; + + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; + + const testDemand: DemandPostData = { + owner: '0x0000000000000000000000000000000000000000', + timeFrame: 1, + maxPriceInCentsPerMwh: 100, + currency: 'USD', + location: ['Thailand;Central;Nakhon Pathom'], + deviceType: ['Solar'], + otherGreenAttributes: 'string', + typeOfPublicSupport: 'string', + energyPerTimeFrame: 10, + registryCompliance: 'I-REC', + startTime: moment().unix(), + endTime: moment().add(1, 'month').unix(), + procureFromSingleFacility: true, + automaticMatching: true, + vintage: [2019, 2020] + }; + + beforeEach(async () => { + apiServer = await startAPI(); + }); + + afterEach(async () => { + await apiServer.close(); + await sleep(100); + + try { + fs.unlinkSync('db.sqlite'); + } catch (err) {} + }); + + describe('POST', () => { + it('creates a Demand', async () => { + const postResult = await axios.post(`${BASE_API_URL}/Demand`, testDemand); + + assert.equal(postResult.status, STATUS_CODES.CREATED); + }); + }); +}); diff --git a/packages/origin-backend/src/test/e2e/JsonEntity.test.ts b/packages/origin-backend/src/test/e2e/JsonEntity.test.ts index fe47ee18d2..f796eab61a 100644 --- a/packages/origin-backend/src/test/e2e/JsonEntity.test.ts +++ b/packages/origin-backend/src/test/e2e/JsonEntity.test.ts @@ -17,7 +17,7 @@ describe('JsonEntity API tests', async () => { }); let apiServer: INestApplication; - const BASE_API_URL = `http://localhost:${process.env.PORT}/api`; + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; const entityOwner = '0x24B207fFf1a1097d3c3D69fcE461544f83c6E774'; const testHash = '1d5e7af973fe1387493b2b70e611c57fc3f354e6ec963b811cac529d8ed17288'; diff --git a/packages/origin-backend/src/test/e2e/MarketContractLookup.test.ts b/packages/origin-backend/src/test/e2e/MarketContractLookup.test.ts index 8053df7cb8..ec61f75148 100644 --- a/packages/origin-backend/src/test/e2e/MarketContractLookup.test.ts +++ b/packages/origin-backend/src/test/e2e/MarketContractLookup.test.ts @@ -17,7 +17,7 @@ describe('MarketContractLookup API tests', async () => { }); let apiServer: INestApplication; - const BASE_API_URL = `http://localhost:${process.env.PORT}/api`; + const BASE_API_URL = `http://localhost:${process.env.BACKEND_PORT}/api`; const marketContractLookup = '0x665b25e0edc2d9b5dee75c5f652f92f5b58be12b'; const marketContractLookup2 = '0x123b25e0edc2d9b5dee75c5f652f92f5b58be12b';