diff --git a/src/main.ts b/src/main.ts index febbe34..9181a16 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ async function bootstrap(): Promise { url: 'https://github.com/Space-Game-Engine/Warp-Core/blob/main/docs/install/installation.md', description: 'GitHub installation instructions', }) - .ad.build(); + .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup(localDocUrl, app, document); diff --git a/src/user/resources/resources.resolver.ts b/src/user/resources/resources.resolver.ts index 72abfb9..75d7fa3 100644 --- a/src/user/resources/resources.resolver.ts +++ b/src/user/resources/resources.resolver.ts @@ -18,7 +18,10 @@ export class ResourcesResolver { public resource( @Args('id', {type: () => ID}) id: string, ): Promise { - return this.resourcesService.getSingleResourceById(id); + return this.resourcesService.getSingleResourceById( + this.habitatModel.id, + id, + ); } @Query(() => [HabitatResourceCombined], { @@ -26,7 +29,9 @@ export class ResourcesResolver { name: 'resource_getAll', }) public allResources(): Promise { - return this.resourcesService.getAllResourcesForHabitat(); + return this.resourcesService.getAllResourcesForHabitat( + this.habitatModel.id, + ); } @ResolveField() @@ -35,4 +40,11 @@ export class ResourcesResolver { ): AuthorizedHabitatModel { return this.habitatModel; } + + @ResolveField('productionRate', () => Number) + public async productionRate( + @Parent() habitatResource: HabitatResourceCombined, + ): Promise { + return this.resourcesService.getProductionRateForResource(habitatResource); + } } diff --git a/src/user/resources/service/resources.service.spec.ts b/src/user/resources/service/resources.service.spec.ts index 1a40ab3..dc4c55d 100644 --- a/src/user/resources/service/resources.service.spec.ts +++ b/src/user/resources/service/resources.service.spec.ts @@ -1,21 +1,23 @@ import {Test, TestingModule} from '@nestjs/testing'; import {when} from 'jest-when'; -import {AuthorizedHabitatModel} from '@warp-core/auth'; import {HabitatResourceCombined} from '@warp-core/database/model/habitat-resource.mapped.model'; import {HabitatResourceModel} from '@warp-core/database/model/habitat-resource.model'; -import {HabitatModel} from '@warp-core/database/model/habitat.model'; import {ResourceModel} from '@warp-core/database/model/resource.model'; import {HabitatResourceRepository} from '@warp-core/database/repository/habitat-resource.repository'; +import {CalculationMechanic} from '@warp-core/user/resources/service/calculate/resource-calculation/calculation-mechanic.interface'; import {ResourcesService} from '@warp-core/user/resources/service/resources.service'; jest.mock('@warp-core/database/repository/habitat-resource.repository'); jest.mock('@warp-core/auth/payload/model/habitat.model'); +jest.mock( + '@warp-core/user/resources/service/calculate/resource-calculation/calculation-mechanic.interface', +); describe('Resources service', () => { let resourcesService: ResourcesService; let habitatResourceRepository: jest.Mocked; - let authorizedHabitatModel: AuthorizedHabitatModel; + let calculationMechanic: jest.Mocked; beforeEach(async () => { jest.clearAllMocks(); @@ -23,37 +25,42 @@ describe('Resources service', () => { providers: [ ResourcesService, HabitatResourceRepository, - AuthorizedHabitatModel, + { + provide: CalculationMechanic, + useValue: { + getProductionRate: jest.fn(), + }, + }, ], }).compile(); resourcesService = module.get(ResourcesService); habitatResourceRepository = module.get(HabitatResourceRepository); - authorizedHabitatModel = module.get(AuthorizedHabitatModel); + calculationMechanic = module.get(CalculationMechanic); }); describe('getSingleResourceById', () => { it('should return null when habitat resource was not found', async () => { const resourceId = 'wood'; - authorizedHabitatModel.id = 5; + const habitatId = 5; when(habitatResourceRepository.findOneBy) .calledWith( expect.objectContaining({ - habitatId: authorizedHabitatModel.id, - resourceId: resourceId, + habitatId, + resourceId, }), ) .mockResolvedValue(null); - expect( - resourcesService.getSingleResourceById(resourceId), + return expect( + resourcesService.getSingleResourceById(habitatId, resourceId), ).resolves.toBeNull(); }); it('should return mapped resource type when resource was found', async () => { const resourceId = 'wood'; - authorizedHabitatModel.id = 5; + const habitatId = 5; const resourceModel = { id: resourceId, @@ -65,57 +72,21 @@ describe('Resources service', () => { when(habitatResourceRepository.findOneBy) .calledWith( expect.objectContaining({ - habitatId: authorizedHabitatModel.id, - resourceId: resourceId, + habitatId, + resourceId, }), ) .mockResolvedValue(habitatResourceModel); - expect( - resourcesService.getSingleResourceById(resourceId), + return expect( + resourcesService.getSingleResourceById(habitatId, resourceId), ).resolves.toBeInstanceOf(HabitatResourceCombined); }); }); describe('getAllResourcesForHabitat', () => { - it('should return array of combined habitat resource models for authorized habitat', async () => { - authorizedHabitatModel.id = 5; - - const habitatResourceArray = [ - { - id: '1', - resource: { - id: 'wood', - }, - }, - { - id: '2', - resource: { - id: 'steel', - }, - }, - { - id: '3', - resource: { - id: 'stone', - }, - }, - ] as HabitatResourceModel[]; - - when(habitatResourceRepository.findBy) - .calledWith({ - habitatId: 5, - }) - .mockResolvedValue(habitatResourceArray); - - expect( - await resourcesService.getAllResourcesForHabitat(), - ).toMatchObject(expect.anything()); - }); - - it('should return array of combined habitat resource models for provided habitat', async () => { - const habitatModel = new HabitatModel(); - habitatModel.id = 10; + it('should return array of combined habitat resource models for habitat', async () => { + const habitatId = 5; const habitatResourceArray = [ { @@ -140,13 +111,13 @@ describe('Resources service', () => { when(habitatResourceRepository.findBy) .calledWith({ - habitatId: 10, + habitatId, }) .mockResolvedValue(habitatResourceArray); - expect( - await resourcesService.getAllResourcesForHabitat(habitatModel), - ).toMatchObject(expect.anything()); + return expect( + resourcesService.getAllResourcesForHabitat(habitatId), + ).resolves.toMatchObject(expect.anything()); }); }); }); diff --git a/src/user/resources/service/resources.service.ts b/src/user/resources/service/resources.service.ts index a5d55c3..4256d84 100644 --- a/src/user/resources/service/resources.service.ts +++ b/src/user/resources/service/resources.service.ts @@ -1,25 +1,40 @@ import {Injectable} from '@nestjs/common'; -import {AuthorizedHabitatModel} from '@warp-core/auth'; import {HabitatResourceCombined} from '@warp-core/database/model/habitat-resource.mapped.model'; import {HabitatResourceModel} from '@warp-core/database/model/habitat-resource.model'; -import {HabitatModel} from '@warp-core/database/model/habitat.model'; import {HabitatResourceRepository} from '@warp-core/database/repository/habitat-resource.repository'; +import {CalculationMechanic} from '@warp-core/user/resources/service/calculate/resource-calculation/calculation-mechanic.interface'; @Injectable() export class ResourcesService { constructor( private readonly habitatResourceRepository: HabitatResourceRepository, - private readonly habitatModel: AuthorizedHabitatModel, + private readonly calculationMechanic: CalculationMechanic, ) {} + public async getProductionRateForResource( + habitatResource: HabitatResourceCombined, + ): Promise { + const resource = await this.getHabitatResource( + habitatResource.habitatId, + habitatResource.id, + ); + + if (!resource) { + return 0; + } + + return this.calculationMechanic.getProductionRate(resource); + } + public async getSingleResourceById( - id: string, + habitatId: number, + resourceId: string, ): Promise { - const habitatResource = await this.habitatResourceRepository.findOneBy({ - habitatId: this.habitatModel.id, - resourceId: id, - }); + const habitatResource = await this.getHabitatResource( + habitatId, + resourceId, + ); if (!habitatResource) { return null; @@ -29,19 +44,8 @@ export class ResourcesService { } public async getAllResourcesForHabitat( - habitatModelOrId: HabitatModel | number | null = null, + habitatId: number, ): Promise { - let habitatId: number; - if (habitatModelOrId) { - if (habitatModelOrId instanceof HabitatModel) { - habitatId = habitatModelOrId.id; - } else { - habitatId = habitatModelOrId; - } - } else { - habitatId = this.habitatModel.id; - } - const habitatResources = await this.habitatResourceRepository.findBy({ habitatId, }); @@ -68,4 +72,14 @@ export class ResourcesService { return mappedResource; } + + private getHabitatResource( + habitatId: number, + resourceId: string, + ): Promise { + return this.habitatResourceRepository.findOneBy({ + habitatId, + resourceId, + }); + } } diff --git a/test/e2e/habitat/habitat-creation-onstart-config-buildings-and-resources.spec.ts b/test/e2e/habitat/habitat-creation-onstart-config-buildings-and-resources.spec.ts index ca624d1..d20bd0f 100644 --- a/test/e2e/habitat/habitat-creation-onstart-config-buildings-and-resources.spec.ts +++ b/test/e2e/habitat/habitat-creation-onstart-config-buildings-and-resources.spec.ts @@ -65,7 +65,7 @@ describe('Habitat Creation when onStart config contains buildings and resources' .query({ root: 'resource_getAll', fields: { - fields: ['id', 'currentAmount'], + fields: ['id', 'currentAmount', 'productionRate'], }, }) .send() @@ -77,14 +77,29 @@ describe('Habitat Creation when onStart config contains buildings and resources' resourceId: 'wood', value: 10, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'wood', + property: 'productionRate', + value: 1, + }); expect(resourcesFromResponse).toHaveResourceWithValue({ resourceId: 'stone_granite', value: 20, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'stone_granite', + property: 'productionRate', + value: 0, + }); expect(resourcesFromResponse).toHaveResourceWithValue({ resourceId: 'coal', value: 10, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'coal', + property: 'productionRate', + value: 0, + }); }); it('should have pre-build buildings on building zones', async () => { diff --git a/test/e2e/habitat/habitat-creation-onstart-config-buildings-only.spec.ts b/test/e2e/habitat/habitat-creation-onstart-config-buildings-only.spec.ts index 03380a1..664fe03 100644 --- a/test/e2e/habitat/habitat-creation-onstart-config-buildings-only.spec.ts +++ b/test/e2e/habitat/habitat-creation-onstart-config-buildings-only.spec.ts @@ -53,17 +53,25 @@ describe('Habitat Creation when onStart config contains buildings', () => { .query({ root: 'resource_getAll', fields: { - fields: ['id', 'currentAmount'], + fields: ['id', 'currentAmount', 'productionRate'], }, }) .send() .expect(HttpStatus.OK); - response.body.data.resource_getAll.forEach( + const resourcesFromResponse = response.body.data.resource_getAll; + + resourcesFromResponse.forEach( (singleResource: Partial) => { expect(singleResource.currentAmount).toEqual(0); }, ); + + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'wood', + property: 'productionRate', + value: 1, + }); }); it('should have pre-build buildings on building zones', async () => { diff --git a/test/e2e/habitat/habitat-creation-onstart-config-resources-only.spec.ts b/test/e2e/habitat/habitat-creation-onstart-config-resources-only.spec.ts index d45c669..28016d8 100644 --- a/test/e2e/habitat/habitat-creation-onstart-config-resources-only.spec.ts +++ b/test/e2e/habitat/habitat-creation-onstart-config-resources-only.spec.ts @@ -90,7 +90,7 @@ describe('Habitat Creation when onStart config contains resources', () => { .query({ root: 'resource_getAll', fields: { - fields: ['id', 'currentAmount'], + fields: ['id', 'currentAmount', 'productionRate'], }, }) .send() @@ -102,13 +102,28 @@ describe('Habitat Creation when onStart config contains resources', () => { resourceId: 'wood', value: 100, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'wood', + property: 'productionRate', + value: 0, + }); expect(resourcesFromResponse).toHaveResourceWithValue({ resourceId: 'stone_granite', value: 200, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'stone_granite', + property: 'productionRate', + value: 0, + }); expect(resourcesFromResponse).toHaveResourceWithValue({ resourceId: 'coal', value: 50, }); + expect(resourcesFromResponse).toHaveResourceWithCustomProperty({ + resourceId: 'coal', + property: 'productionRate', + value: 0, + }); }); }); diff --git a/test/e2e/utils/setup-tests.ts b/test/e2e/utils/setup-tests.ts index 58985d0..719f667 100644 --- a/test/e2e/utils/setup-tests.ts +++ b/test/e2e/utils/setup-tests.ts @@ -7,9 +7,17 @@ import {DataSource} from 'typeorm'; import {AppModule} from '@warp-core/app.module'; import {InstallCommand} from '@warp-core/core/install/install.command'; import {InstallModule} from '@warp-core/core/install/install.module'; -import {toHaveResourceWithValue} from '@warp-core/test/expect-extend/resource-assert'; - -expect.extend({toHaveResourceWithValue}); +import { + toHaveResource, + toHaveResourceWithCustomProperty, + toHaveResourceWithValue, +} from '@warp-core/test/expect-extend/resource-assert'; + +expect.extend({ + toHaveResource, + toHaveResourceWithValue, + toHaveResourceWithCustomProperty, +}); export async function createNestApplicationE2E(): Promise { const module = await Test.createTestingModule({ diff --git a/test/expect-extend/resource-assert.ts b/test/expect-extend/resource-assert.ts index df16c67..028f278 100644 --- a/test/expect-extend/resource-assert.ts +++ b/test/expect-extend/resource-assert.ts @@ -1,15 +1,29 @@ -import CustomMatcher = jest.CustomMatcher; +import CustomMatcherResult = jest.CustomMatcherResult; import {HabitatResourceCombined} from '@warp-core/database/model/habitat-resource.mapped.model'; export type ResourceCheck = { resourceId: string; value: number; }; +export type ResourceWithCustomPropertyCheck = { + resourceId: string; + property: string; + value: number; +}; + +function getResourceFromReceivedResources( + received: HabitatResourceCombined[], + resourceId: string, +): undefined | Partial { + return received.find(singleResource => { + return singleResource.id === resourceId; + }); +} -export const toHaveResourceWithValue: CustomMatcher = ( +export function toHaveResource( received: unknown, - actual: ResourceCheck, -) => { + actual: {resourceId: string}, +): CustomMatcherResult { if (!received) { return { pass: false, @@ -24,11 +38,12 @@ export const toHaveResourceWithValue: CustomMatcher = ( }; } - const {resourceId, value} = actual; + const {resourceId} = actual; - const resourceToCheck = received.find(singleResource => { - return singleResource.id === resourceId; - }) as undefined | Partial; + const resourceToCheck = getResourceFromReceivedResources( + received, + resourceId, + ); if (!resourceToCheck) { return { @@ -45,9 +60,56 @@ export const toHaveResourceWithValue: CustomMatcher = ( }; } + return { + pass: true, + message: () => '', + }; +} + +export function toHaveResourceWithValue( + received: unknown, + actual: ResourceCheck, +): CustomMatcherResult { + const resourceAssert = toHaveResource(received, actual); + + if (!resourceAssert.pass) { + return resourceAssert; + } + + const {resourceId, value} = actual; + + const resourceToCheck = getResourceFromReceivedResources( + received as HabitatResourceCombined[], + resourceId, + )!; + return { pass: value === resourceToCheck.currentAmount, message: () => `Resource "${resourceId}" amount should be ${value}, actual value is ${resourceToCheck.currentAmount}`, }; -}; +} + +export function toHaveResourceWithCustomProperty( + received: unknown, + actual: ResourceWithCustomPropertyCheck, +): CustomMatcherResult { + const resourceAssert = toHaveResource(received, actual); + + if (!resourceAssert.pass) { + return resourceAssert; + } + + const {resourceId, value, property} = actual; + + const resourceToCheck = getResourceFromReceivedResources( + received as HabitatResourceCombined[], + resourceId, + )!; + + return { + pass: value === resourceToCheck[property], + message: () => + `Resource "${resourceId}" for field "${property}" should be ${value}, actual value is ${resourceToCheck[property]}`, + }; +} diff --git a/test/jest.d.ts b/test/jest.d.ts index 4104a66..c69f190 100644 --- a/test/jest.d.ts +++ b/test/jest.d.ts @@ -1,12 +1,17 @@ import { ResourceCheck, + ResourceWithCustomPropertyCheck, + toHaveResource, + toHaveResourceWithCustomProperty, toHaveResourceWithValue, } from '@warp-core/test/expect-extend/resource-assert'; declare global { namespace jest { interface Matchers { + toHaveResource(expected: {id: string}): R toHaveResourceWithValue(expected: ResourceCheck): R + toHaveResourceWithCustomProperty(expected: ResourceWithCustomPropertyCheck): R } } }