From 80d8c34dd4cc031e334c378a33628fdba1895f06 Mon Sep 17 00:00:00 2001 From: Alex Graf Date: Tue, 9 Apr 2024 13:50:13 +0200 Subject: [PATCH 1/3] #67: update elasticsearch-index local --- .../app/scripts/export-to-elasticsearch.ts | 184 +++--------------- 1 file changed, 32 insertions(+), 152 deletions(-) diff --git a/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts b/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts index f4f38b98..ecce5bdb 100644 --- a/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts +++ b/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts @@ -11,6 +11,7 @@ import { Eq as EqString } from 'fp-ts/string'; import * as D from 'io-ts/Decoder'; import * as G from 'io-ts/Guard'; import { Overwrite } from 'type-zoo'; +import * as dotenv from 'dotenv'; import { isNotNil } from '../../../../../libs/core/src/'; import { ElasticSearchAsset } from '../../../../../libs/shared/src/'; @@ -26,7 +27,27 @@ export const date: D.Decoder = D.fromGuard(dateGuard, 'Date'); export const dateIdFromDate = (d: Date) => (d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()) as DateId; export const DateIdFromDate = pipe(date, D.map(dateIdFromDate)); -const index = 'asset-swissgeol-asset'; +dotenv.config(); + +const index = 'swissgeol_asset_asset'; + +const mappingProperties = { + assetId: { type: 'integer' }, + properties: { + assetId: {type: "integer" }, + assetKindItemCode: { type: "keyword" }, + authorIds: { type: "integer" }, + contactNames: { type: "text",fields: {keyword: {type: "keyword",ignore_above: 256}}}, + createDate: { type: "long" }, + createDateId: { type: "integer" }, + languageItemCode: { type: "keyword" }, + manCatLabelItemCodes: { type: "keyword" }, + sgsId: { type: "keyword" }, + titleOriginal: { type: "text", fields: { keyword: { type: "keyword", ignore_above: 256 }} }, + titlePublic: {type: "text",fields: {keyword: {type: "keyword",ignore_above: 256}}}, + usageCode: { type: "keyword" } + } + }; const main = async () => { try { @@ -41,35 +62,21 @@ const main = async () => { } : {}), }; - // console.log({ options }); + console.log({ options }); const client = new Client(options); - // console.log('connected'); - - // console.log({ client }); + console.log('connected'); + const exists = await client.indices.exists({ index }); - if (exists) { - await client.indices.delete({ index }); + if (!exists) { + /* await client.indices.create({ + index, + mappings: mappingProperties + });*/ } - await client.indices.create({ - index, - mappings: { - properties: { - assetId: { type: 'integer' }, - createDateId: { type: 'integer' }, - authorIds: { type: 'integer' }, - assetKindItemCode: { type: 'keyword' }, - languageItemCode: { type: 'keyword' }, - usageCode: { type: 'keyword' }, - manCatLabelItemCodes: { type: 'keyword' }, - sgsId: { type: 'keyword' }, - }, - }, - }); - const prisma = new PrismaClient(); const RawAsset = D.struct({ @@ -173,141 +180,16 @@ const main = async () => { ), ); - // const assets = pipe( - // dataset, - // NEA.group(EqRawAssetByAssetId), - // NEA.fromArray, - // O.map( - // flow( - // A.filter(a => a.length > 1), - // A.splitAt(3), - // a => a[0], - // ), - // ), - // ); - // const dataset = await prisma.asset.findMany({ - // // where: { assetId: { lt: 10 } }, - // // where: { AND: [{ assetId: { gte: 0 } }, { assetId: { lte: 32768 } }] }, - // take: 32000, - // select: { - // assetId: true, - // titlePublic: true, - // assetContacts: { - // where: { role: 'author' }, - // select: { - // contact: { - // select: { - // name: true, - // }, - // }, - // }, - // }, - // }, - // }); - // const dataset = await prisma.asset.findMany({ - // select: { - // assetId: true, - // titlePublic: true, - // // internalUses: { - // // select: { - // // isAvailable: true, - // // }, - // // }, - // }, - // // include: { - // // languageItem: true, - // // }, - // }); - // - // - - // const dataset2 = dataset.map(x => ({ - // assetId: x.assetId, - // titlePublic: x.titlePublic, - // authors: x.assetContacts.map(y => y.contact.name), - // })); - - // console.log((dataset as any).filter((a: any) => !a.authorName)); - // console.log(dataset.length); - // - // console.log(JSON.stringify(assets, null, 4)); if (E.isRight(assets)) { - // console.log(assets.right.slice(1, 10)); - // console.log(assets.right.filter(a => a.assetId === 38981)[0]); const operations = assets.right.flatMap(doc => [ { index: { _index: 'swissgeol_asset_asset', _id: doc.assetId } }, doc, ]); - // console.log(operations[0]); - - // console.log(assets.right.filter(a => a.assetId === 34153)[0]); - - // console.log(operations); - // + const bulkResponse = await client.bulk({ refresh: true, operations }); console.log(bulkResponse); - // - // const SearchResults = D.struct({ - // hits: D.struct({ - // hits: D.array( - // D.struct({ - // fields: D.struct({ - // assetId: D.array(D.number), - // }), - // }), - // ), - // }), - // aggregations: D.struct({ - // authors: D.struct({ - // buckets: D.array(D.struct({ key: D.string, doc_count: D.number })), - // }), - // }), - // }); - - // const foo = pipe( - // TE.tryCatch( - // () => - // client.search({ - // // size: 10000, - // query: { - // bool: { - // must: { - // query_string: { - // query: 'Schenker', - // fields: ['titlePublic', 'titleOriginal', 'contactNames'], - // }, - // }, - // }, - // }, - // aggs: { - // authorIds: { terms: { field: 'authorIds' } }, - // minCreateDate: { min: { field: 'createDate' } }, - // maxCreateDate: { max: { field: 'createDate' } }, - // }, - // fields: [ - // 'assetId', - // 'titlePublic', - // 'titleOriginal', - // 'createDate', - // 'authorIds', - // 'contactNames', - // ], - // _source: false, - // }), - // unknownToError, - // ), - // // TE.chainW(flow(SearchResults.decode, E.mapLeft(D.draw), TE.fromEither)), - // // TE.mapLeft(e => console.log(JSON.stringify(e, null, 4))), - // ); - - // foo().then(a => { - // if (a._tag === 'Right') { - // console.log(a.right.aggregations); - // } - // }); - // console.log('hi', JSON.stringify(results, null, 4)); - } else { + } else { console.log(D.draw(assets.left)); } } catch (e) { @@ -316,5 +198,3 @@ const main = async () => { }; main().then(() => console.log('done')); - -// client.indices.delete({ index: 'asset' }); From 1f6eb0dd4c1206f67942942b38e4df75415e6334 Mon Sep 17 00:00:00 2001 From: Alex Graf Date: Tue, 9 Apr 2024 14:03:24 +0200 Subject: [PATCH 2/3] fix lint --- .../app/scripts/export-to-elasticsearch.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts b/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts index ecce5bdb..095b3b0f 100644 --- a/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts +++ b/apps/server-asset-sg/src/app/scripts/export-to-elasticsearch.ts @@ -11,7 +11,6 @@ import { Eq as EqString } from 'fp-ts/string'; import * as D from 'io-ts/Decoder'; import * as G from 'io-ts/Guard'; import { Overwrite } from 'type-zoo'; -import * as dotenv from 'dotenv'; import { isNotNil } from '../../../../../libs/core/src/'; import { ElasticSearchAsset } from '../../../../../libs/shared/src/'; @@ -27,28 +26,8 @@ export const date: D.Decoder = D.fromGuard(dateGuard, 'Date'); export const dateIdFromDate = (d: Date) => (d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()) as DateId; export const DateIdFromDate = pipe(date, D.map(dateIdFromDate)); -dotenv.config(); - const index = 'swissgeol_asset_asset'; -const mappingProperties = { - assetId: { type: 'integer' }, - properties: { - assetId: {type: "integer" }, - assetKindItemCode: { type: "keyword" }, - authorIds: { type: "integer" }, - contactNames: { type: "text",fields: {keyword: {type: "keyword",ignore_above: 256}}}, - createDate: { type: "long" }, - createDateId: { type: "integer" }, - languageItemCode: { type: "keyword" }, - manCatLabelItemCodes: { type: "keyword" }, - sgsId: { type: "keyword" }, - titleOriginal: { type: "text", fields: { keyword: { type: "keyword", ignore_above: 256 }} }, - titlePublic: {type: "text",fields: {keyword: {type: "keyword",ignore_above: 256}}}, - usageCode: { type: "keyword" } - } - }; - const main = async () => { try { const options = { @@ -68,15 +47,6 @@ const main = async () => { console.log('connected'); - - const exists = await client.indices.exists({ index }); - if (!exists) { - /* await client.indices.create({ - index, - mappings: mappingProperties - });*/ - } - const prisma = new PrismaClient(); const RawAsset = D.struct({ From 4edf1d105cf75f8ff5637bdb8df9ceab33e10ee5 Mon Sep 17 00:00:00 2001 From: Alex Graf Date: Tue, 9 Apr 2024 17:02:49 +0200 Subject: [PATCH 3/3] #11 fix sonar-issues, unit-tests --- apps/server-asset-sg/jest.config.ts | 1 + apps/server-asset-sg/project.json | 3 +- .../contact-edit/contact-edit.service.spec.ts | 107 ++++++++++++++++++ development/init/elasticsearch/index | 1 - libs/asset-editor/jest.config.ts | 2 +- .../asset-search-detail.component.html | 2 + .../components/button/button.component.scss | 6 +- .../ngrx-store-logger.spec.ts | 17 +++ .../serialize-error/serialize-error.spec.ts | 68 +++++++++++ .../lib/services/favourite.service.spec.ts | 40 +++++++ libs/ngx-kobalte/tsconfig.spec.json | 3 +- 11 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 apps/server-asset-sg/src/app/contact-edit/contact-edit.service.spec.ts create mode 100644 libs/core/src/lib/ngrx-store-logger/ngrx-store-logger.spec.ts create mode 100644 libs/core/src/lib/serialize-error/serialize-error.spec.ts create mode 100644 libs/favourite/src/lib/services/favourite.service.spec.ts diff --git a/apps/server-asset-sg/jest.config.ts b/apps/server-asset-sg/jest.config.ts index 89c6d013..c8589bef 100644 --- a/apps/server-asset-sg/jest.config.ts +++ b/apps/server-asset-sg/jest.config.ts @@ -13,4 +13,5 @@ export default { }, moduleFileExtensions: ['ts', 'js', 'html'], coverageDirectory: '../../coverage/apps/server-asset-sg', + coverageReporters: ['json', 'text', 'cobertura', 'lcov'], }; diff --git a/apps/server-asset-sg/project.json b/apps/server-asset-sg/project.json index 20b858ad..f3c9db39 100644 --- a/apps/server-asset-sg/project.json +++ b/apps/server-asset-sg/project.json @@ -77,7 +77,8 @@ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "apps/server-asset-sg/jest.config.ts", - "passWithNoTests": true + "passWithNoTests": true, + "codeCoverage": true } }, "gen-prisma-client": { diff --git a/apps/server-asset-sg/src/app/contact-edit/contact-edit.service.spec.ts b/apps/server-asset-sg/src/app/contact-edit/contact-edit.service.spec.ts new file mode 100644 index 00000000..d2f7eed7 --- /dev/null +++ b/apps/server-asset-sg/src/app/contact-edit/contact-edit.service.spec.ts @@ -0,0 +1,107 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { left, right } from 'fp-ts/Either'; + +import { Contact, PatchContact } from '@asset-sg/shared'; + +import { PrismaService } from '../prisma/prisma.service'; + +import { ContactEditService } from './contact-edit.service'; + + +jest.mock('../prisma/prisma.service'); + +const mockPatchContact: PatchContact = { + name: 'John Doe', + street: '123 Main Street', + houseNumber: '1A', + plz: '12345', + locality: 'City', + country: 'Country', + telephone: '123-456-7890', + email: 'john.doe@example.com', + website: 'www.example.com', + contactKindItemCode: '123' +}; +const mockContact: Contact = { + id: 1, + contactKindItemCode: '123', + name: 'John Doe', + street: '123 Main Street', + houseNumber: '1A', + plz: '12345', + locality: 'City', + country: 'Country', + telephone: '123-456-7890', + email: 'john.doe@example.com', + website: 'www.example.com' +}; + +const mockPrisma = { + contact: {} +}; + + +describe('ContactEditService', () => { + let service: ContactEditService; + let prismaService: PrismaService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ContactEditService, + { + provide: PrismaService, + useValue: mockPrisma, + }, + ], + }).compile(); + + service = module.get(ContactEditService); + prismaService = module.get(PrismaService); + + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should create a contact', async () => { + prismaService.contact.create = jest.fn().mockResolvedValue(mockContact); + + const result = await service.createContact(mockPatchContact)(); + + expect(result).toEqual(right(mockContact)); + expect(prismaService.contact.create).toHaveBeenCalledWith({ data: mockPatchContact }); + }); + + it('should update a contact', async () => { + const mockContactId = 1; + + prismaService.contact.update = jest.fn().mockResolvedValue(mockPatchContact); + prismaService.contact.findFirstOrThrow = jest.fn().mockResolvedValue(mockContact); + + const result = await service.updateContact(mockContactId, mockContact)(); + + expect(result).toEqual(right(mockContact)); + expect(prismaService.contact.update).toHaveBeenCalledWith({ where: { contactId: mockContactId }, data: mockContact }); + }); + + it('should handle errors when creating a contact', async () => { + prismaService.contact.create = jest.fn().mockRejectedValue(new Error('Test error')); + + const result = await service.createContact(mockContact)(); + + expect(result).toEqual(left(new Error())); + expect(prismaService.contact.create).toHaveBeenCalledWith({ data: mockContact }); + }); + + it('should handle errors when updating a contact', async () => { + const mockContactId = 1; + prismaService.contact.update = jest.fn().mockRejectedValue(new Error('Test error')); + + const result = await service.updateContact(mockContactId, mockContact)(); + + expect(result).toEqual(left(new Error())); + expect(prismaService.contact.update).toHaveBeenCalledWith({ where: { contactId: mockContactId }, data: mockContact }); + }); +}); \ No newline at end of file diff --git a/development/init/elasticsearch/index b/development/init/elasticsearch/index index 6c9b239c..c4d29785 100644 --- a/development/init/elasticsearch/index +++ b/development/init/elasticsearch/index @@ -1,6 +1,5 @@ PUT /swissgeol_asset_asset - PUT /swissgeol_asset_asset/_mapping { "properties": { diff --git a/libs/asset-editor/jest.config.ts b/libs/asset-editor/jest.config.ts index d3dbc386..ec24c907 100644 --- a/libs/asset-editor/jest.config.ts +++ b/libs/asset-editor/jest.config.ts @@ -13,7 +13,7 @@ export default { transform: { '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular', }, - transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$|ol)'], snapshotSerializers: [ 'jest-preset-angular/build/serializers/no-ng-attributes', 'jest-preset-angular/build/serializers/ng-snapshot', diff --git a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html index 47cede1a..44c59734 100644 --- a/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html +++ b/libs/asset-viewer/src/lib/components/asset-search-detail/asset-search-detail.component.html @@ -41,6 +41,7 @@ +
@@ -173,6 +174,7 @@ + diff --git a/libs/client-shared/src/lib/components/button/button.component.scss b/libs/client-shared/src/lib/components/button/button.component.scss index ba728cb3..a4b49108 100644 --- a/libs/client-shared/src/lib/components/button/button.component.scss +++ b/libs/client-shared/src/lib/components/button/button.component.scss @@ -66,7 +66,7 @@ background-color: #87d7e6; color: #357183; } - &:active:not(focus) { + &:active:not(:focus) { box-shadow: none; background-color: variables.$cyan-06; color: variables.$white; @@ -110,7 +110,7 @@ &:focus { color: variables.$red; } - &:active:not(focus) { + &:active:not(:focus) { color: variables.$dark-red; } } @@ -135,7 +135,7 @@ &:focus { color: variables.$red; } - &:active:not(focus) { + &:active:not(:focus) { color: variables.$dark-red; } } diff --git a/libs/core/src/lib/ngrx-store-logger/ngrx-store-logger.spec.ts b/libs/core/src/lib/ngrx-store-logger/ngrx-store-logger.spec.ts new file mode 100644 index 00000000..8107c3a8 --- /dev/null +++ b/libs/core/src/lib/ngrx-store-logger/ngrx-store-logger.spec.ts @@ -0,0 +1,17 @@ +import { ActionReducer } from '@ngrx/store'; + +import { storeLogger } from './ngrx-store-logger'; + +describe('storeLogger', () => { + + it('should log state and action', () => { + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { /* noop */ }); + const metaReducer = storeLogger(); + const actionReducer: ActionReducer = (state: string | undefined, action: any) => { return state || ''; }; + + metaReducer(actionReducer)('state', { type: 'test' }); + + expect(consoleLogSpy).toHaveBeenCalled(); + }); + +}); \ No newline at end of file diff --git a/libs/core/src/lib/serialize-error/serialize-error.spec.ts b/libs/core/src/lib/serialize-error/serialize-error.spec.ts new file mode 100644 index 00000000..47978365 --- /dev/null +++ b/libs/core/src/lib/serialize-error/serialize-error.spec.ts @@ -0,0 +1,68 @@ +import { NonError, deserializeError, isErrorLike, serializeError} from './serialize-error'; + +describe('serializeError', () => { + it('should serialize error objects', () => { + const error = new Error('Test error'); + const serialized = serializeError(error); + expect(serialized).toHaveProperty('name', 'Error'); + expect(serialized).toHaveProperty('message', 'Test error'); + expect(serialized).toHaveProperty('stack'); + }); + + it('should serialize non-error objects', () => { + const obj = { key: 'value' }; + const serialized = serializeError(obj); + expect(serialized).toEqual(obj); + }); + + it('should serialize primitive values', () => { + expect(serializeError('test')).toBe('test'); + expect(serializeError(123)).toBe(123); + }); +}); + +describe('deserializeError', () => { + it('should deserialize serialized error objects', () => { + const error = new Error('Test error'); + const serialized = serializeError(error); + const deserialized = deserializeError(serialized); + expect(deserialized).toBeInstanceOf(Error); + expect(deserialized.message).toBe('Test error'); + }); + + it('should return NonError for non-error objects', () => { + const obj = { key: 'value' }; + const deserialized = deserializeError(obj); + expect(deserialized).toBeInstanceOf(NonError); + expect(deserialized.message).toBe(JSON.stringify(obj)); + }); + + it('should return NonError for primitive string', () => { + const deserialized = deserializeError('test'); + expect(deserialized).toBeInstanceOf(NonError); + expect(deserialized.message).toBe('"test"'); + }); + + it('should return NonError for primitive number', () => { + const deserialized = deserializeError(123); + expect(deserialized).toBeInstanceOf(NonError); + expect(deserialized.message).toBe('123'); + }); +}); + +describe('isErrorLike', () => { + it('should return true for error objects', () => { + const error = new Error('Test error'); + expect(isErrorLike(error)).toBe(true); + }); + + it('should return false for non-error objects', () => { + const obj = { key: 'value' }; + expect(isErrorLike(obj)).toBe(false); + }); + + it('should return false for primitive values', () => { + expect(isErrorLike('test')).toBe(false); + expect(isErrorLike(123)).toBe(false); + }); +}); \ No newline at end of file diff --git a/libs/favourite/src/lib/services/favourite.service.spec.ts b/libs/favourite/src/lib/services/favourite.service.spec.ts new file mode 100644 index 00000000..0c192f80 --- /dev/null +++ b/libs/favourite/src/lib/services/favourite.service.spec.ts @@ -0,0 +1,40 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { Favourite, FavouriteService } from './favourite.service'; + +describe('FavouriteService', () => { + let service: FavouriteService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [FavouriteService] + }); + + service = TestBed.inject(FavouriteService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); // Ensure that there are no outstanding requests + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should retrieve favourites from API via GET', () => { + const dummyFavourites: Favourite[] = [{}, {}, {}]; + + service.getFavourites().subscribe(favourites => { + expect(favourites.length).toBe(3); + expect(favourites).toEqual(dummyFavourites); + }); + + const request = httpMock.expectOne(`/api/user/favourite`); + expect(request.request.method).toBe('GET'); + request.flush(dummyFavourites); + }); +}); \ No newline at end of file diff --git a/libs/ngx-kobalte/tsconfig.spec.json b/libs/ngx-kobalte/tsconfig.spec.json index 20c8d279..89870c6c 100644 --- a/libs/ngx-kobalte/tsconfig.spec.json +++ b/libs/ngx-kobalte/tsconfig.spec.json @@ -14,6 +14,7 @@ "src/**/*.spec.js", "src/**/*.test.jsx", "src/**/*.spec.jsx", - "src/**/*.d.ts" + "src/**/*.d.ts", + "src/lib/components/list-box.s.ts" ] }
{{ statusWork.statusWorkDate | assetSgDate }} {{ statusWork.statusWork | valueItemName }}