From 1f40000476b5c83e71dabce2e3889adb8022daea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rard=20Dethier?= Date: Tue, 1 Aug 2023 11:12:20 +0200 Subject: [PATCH] feat: use Hash class. logion-network/logion-internal#949 --- package.json | 2 +- .../controllers/adapters/locrequestadapter.ts | 8 +- .../controllers/collection.controller.ts | 116 ++++++++------ src/logion/controllers/fileupload.ts | 7 +- .../controllers/locrequest.controller.ts | 64 ++++---- src/logion/controllers/lofile.controller.ts | 3 +- src/logion/controllers/records.controller.ts | 111 +++++++------ src/logion/lib/crypto/hashing.ts | 10 +- src/logion/model/collection.model.ts | 42 ++--- src/logion/model/locrequest.model.ts | 68 ++++---- src/logion/model/tokensrecord.model.ts | 49 +++--- src/logion/services/collection.service.ts | 14 +- src/logion/services/idenfy/idenfy.service.ts | 4 +- .../services/locsynchronization.service.ts | 18 +-- src/logion/services/seal.service.ts | 8 +- src/logion/services/tokensrecord.service.ts | 17 +- test/helpers/Mock.ts | 6 + test/integration/migration/migration.spec.ts | 16 +- .../model/collection.model.spec.ts | 33 ++-- .../model/locrequest.model.spec.ts | 45 +++--- .../controllers/collection.controller.spec.ts | 149 +++++++++--------- test/unit/controllers/fileupload.spec.ts | 7 +- .../locrequest.controller.creation.spec.ts | 2 +- .../locrequest.controller.fetch.spec.ts | 9 +- .../locrequest.controller.items.spec.ts | 56 +++---- .../locrequest.controller.shared.ts | 4 +- .../locrequest.controller.sof.spec.ts | 13 +- .../controllers/lofile.controller.spec.ts | 4 +- .../controllers/records.controller.spec.ts | 103 ++++++------ test/unit/lib/crypto/hashing.spec.ts | 11 +- test/unit/model/collection.model.spec.ts | 9 +- test/unit/model/locrequest.model.spec.ts | 149 +++++++++--------- .../locsynchronization.service.spec.ts | 40 ++--- test/unit/services/seal.service.spec.ts | 9 +- yarn.lock | 10 +- 35 files changed, 647 insertions(+), 569 deletions(-) create mode 100644 test/helpers/Mock.ts diff --git a/package.json b/package.json index 76e1a0d5..21419156 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "typeorm": "node ./node_modules/typeorm/cli.js -d ./dist/db-tools/ormconfig.js" }, "dependencies": { - "@logion/node-api": "^0.18.0-1", + "@logion/node-api": "^0.18.0-2", "@logion/node-exiftool": "^2.3.1-3", "@logion/rest-api-core": "^0.4.3", "@polkadot/wasm-crypto": "^7.1.1", diff --git a/src/logion/controllers/adapters/locrequestadapter.ts b/src/logion/controllers/adapters/locrequestadapter.ts index 36a67593..09c5d296 100644 --- a/src/logion/controllers/adapters/locrequestadapter.ts +++ b/src/logion/controllers/adapters/locrequestadapter.ts @@ -7,7 +7,7 @@ import { UserIdentity } from "../../model/useridentity.js"; import { components } from "../components.js"; import { VoteRepository, VoteAggregateRoot } from "../../model/vote.model.js"; import { VerifiedIssuerAggregateRoot, VerifiedIssuerSelectionRepository } from "../../model/verifiedissuerselection.model.js"; -import { Fees } from "@logion/node-api"; +import { Fees, Hash } from "@logion/node-api"; import { SupportedAccountId } from "../../model/supportedaccountid.model.js"; export type UserPrivateData = { @@ -80,7 +80,7 @@ export class LocRequestAdapter { closedOn: request.closedOn || undefined, files: request.getFiles(viewer).map(file => ({ name: file.name, - hash: file.hash, + hash: file.hash.toHex(), nature: file.nature, submitter: file.submitter, restrictedDelivery: file.restrictedDelivery, @@ -92,7 +92,7 @@ export class LocRequestAdapter { })), metadata: request.getMetadataItems(viewer).map(item => ({ name: item.name, - nameHash: item.nameHash, + nameHash: item.nameHash.toHex(), value: item.value, submitter: item.submitter, fees: toFeesView(item.fees), @@ -104,7 +104,7 @@ export class LocRequestAdapter { addedOn: link.addedOn?.toISOString() || undefined, fees: toFeesView(link.fees), })), - seal: locDescription.seal?.hash, + seal: locDescription.seal?.hash ? locDescription.seal.hash.toHex() : undefined, company: locDescription.company, iDenfy, voteId: vote?.voteId, diff --git a/src/logion/controllers/collection.controller.ts b/src/logion/controllers/collection.controller.ts index 5bc84eb3..60b62b57 100644 --- a/src/logion/controllers/collection.controller.ts +++ b/src/logion/controllers/collection.controller.ts @@ -1,4 +1,6 @@ import { AuthenticatedUser } from "@logion/authenticator"; +import { Hash } from "@logion/node-api"; +import { HexString } from "@polkadot/util/types"; import { injectable } from "inversify"; import { Controller, ApiController, Async, HttpGet, HttpPost, SendsResponse, HttpPut, HttpDelete } from "dinoloop"; import { @@ -131,11 +133,11 @@ export class CollectionController extends ApiController { const { collectionLocId, itemId, description, addedOn } = collectionItem; return { collectionLocId, - itemId, + itemId: itemId.toHex(), description, addedOn: addedOn?.toISOString(), files: collectionItem.files?.map(file => ({ - hash: file.hash, + hash: file.hash.toHex(), name: file.name, contentType: file.contentType, uploaded: file.cid !== undefined, @@ -165,7 +167,7 @@ export class CollectionController extends ApiController { @HttpGet('/:collectionLocId/items/:itemId') @Async() async getCollectionItem(_body: any, collectionLocId: string, itemId: string): Promise { - const collectionItem = await this.collectionRepository.findBy(collectionLocId, itemId); + const collectionItem = await this.collectionRepository.findBy(collectionLocId, Hash.fromHex(itemId)); if (collectionItem) { return this.toView(collectionItem.getDescription()); } else { @@ -176,7 +178,7 @@ export class CollectionController extends ApiController { @HttpPost('/:collectionLocId/items') @Async() async submitItemPublicData(body: CreateCollectionItemView, collectionLocId: string): Promise { - const itemId = requireDefined(body.itemId); + const itemId = Hash.fromHex(requireDefined(body.itemId)); const existingItem = await this.collectionRepository.findBy(collectionLocId, itemId); if(existingItem) { throw badRequest("Cannot replace existing item, you may try to cancel it first"); @@ -188,7 +190,7 @@ export class CollectionController extends ApiController { itemId, description, files: body.files?.map(file => ({ - hash: requireDefined(file.hash), + hash: Hash.fromHex(requireDefined(file.hash)), name: requireDefined(file.name), contentType: requireDefined(file.contentType), })), @@ -208,12 +210,15 @@ export class CollectionController extends ApiController { @HttpDelete('/:collectionLocId/items/:itemId') @Async() async cancelItem(_body: any, collectionLocId: string, itemId: string): Promise { - const publishedCollectionItem = await this.logionNodeCollectionService.getCollectionItem({ collectionLocId, itemId }); + const publishedCollectionItem = await this.logionNodeCollectionService.getCollectionItem({ + collectionLocId, + itemId: Hash.fromHex(itemId as HexString), + }); if (publishedCollectionItem) { throw badRequest("Collection Item already published, cannot be cancelled"); } - const item = await this.collectionRepository.findBy(collectionLocId, itemId); + const item = await this.collectionRepository.findBy(collectionLocId, Hash.fromHex(itemId)); if(!item) { throw badRequest(`Collection Item ${ collectionLocId } ${ itemId } not found`); } @@ -222,7 +227,7 @@ export class CollectionController extends ApiController { } static uploadFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemId}/files"].post!; + const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemIdHex}/files"].post!; operationObject.summary = "Uploads a Collection Item's file"; operationObject.description = "The authenticated user must be the requester of the LOC."; operationObject.responses = getDefaultResponsesNoContent(); @@ -232,23 +237,27 @@ export class CollectionController extends ApiController { }); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'itemId': "The ID of the Collection Item", + 'itemIdHex': "The ID of the Collection Item", }); } - @HttpPost('/:collectionLocId/:itemId/files') + @HttpPost('/:collectionLocId/:itemIdHex/files') @Async() - async uploadFile(body: FileUploadData, collectionLocId: string, itemId: string): Promise { + async uploadFile(body: FileUploadData, collectionLocId: string, itemIdHex: string): Promise { const collectionLoc = requireDefined(await this.locRequestRepository.findById(collectionLocId), () => badRequest(`Collection ${ collectionLocId } not found`)); await this.authenticationService.authenticatedUserIs(this.request, collectionLoc.requesterAddress); - const publishedCollectionItem = await this.logionNodeCollectionService.getCollectionItem({ collectionLocId, itemId }) + const itemId = Hash.fromHex(itemIdHex); + const publishedCollectionItem = await this.logionNodeCollectionService.getCollectionItem({ + collectionLocId, + itemId, + }); if (!publishedCollectionItem) { throw badRequest("Collection Item not found on chain") } - const hash = requireDefined(body.hash, () => badRequest("No hash found for upload file")); + const hash = Hash.fromHex(requireDefined(body.hash, () => badRequest("No hash found for upload file"))); const file = await getUploadedFile(this.request, hash); const publishedCollectionItemFile = await this.getCollectionItemFile({ @@ -265,7 +274,7 @@ export class CollectionController extends ApiController { const collectionItem = await this.collectionRepository.findBy(collectionLocId, itemId); if(!collectionItem) { - throw badRequest(`Collection item ${ collectionLocId }/${ itemId } not found`); + throw badRequest(`Collection item ${ collectionLocId }/${ itemId.toHex() } not found`); } const itemFile = collectionItem.file(hash); if (!itemFile) { @@ -283,7 +292,7 @@ export class CollectionController extends ApiController { private async getCollectionItemFile(params: GetCollectionItemFileParams): Promise { const dbItem = await this.collectionRepository.findBy(params.collectionLocId, params.itemId); - const dbFile = dbItem?.getDescription().files?.find(file => file.hash === params.hash); + const dbFile = dbItem?.getDescription().files?.find(file => file.hash.equalTo(params.hash)); const chainFile = await this.logionNodeCollectionService.getCollectionItemFile(params); if (chainFile && dbFile) { return { @@ -296,22 +305,24 @@ export class CollectionController extends ApiController { } static downloadItemFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemId}/files/{hash}"].get!; + const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemIdHex}/files/{hashHex}"].get!; operationObject.summary = "Downloads a copy of a file of the Collection Item"; operationObject.description = "The authenticated user must be the owner of the underlying token"; operationObject.responses = getDefaultResponsesWithAnyBody(); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'itemId': "The ID of the Collection Item", - 'hash': "The hash of the file", + 'itemIdHex': "The ID of the Collection Item", + 'hashHex': "The hash of the file", }); } - @HttpGet('/:collectionLocId/:itemId/files/:hash') + @HttpGet('/:collectionLocId/:itemIdHex/files/:hashHex') @Async() @SendsResponse() - async downloadItemFile(_body: any, collectionLocId: string, itemId: string, hash: string): Promise { + async downloadItemFile(_body: any, collectionLocId: string, itemIdHex: string, hashHex: string): Promise { const authenticated = await this.authenticationService.authenticatedUser(this.request); + const itemId = Hash.fromHex(itemIdHex); + const hash = Hash.fromHex(hashHex); const collectionItem = await this.checkCanDownloadItemFile(authenticated, collectionLocId, itemId, hash); const publishedCollectionItemFile = await this.getCollectionItemFile({ @@ -351,7 +362,7 @@ export class CollectionController extends ApiController { }); } - private async checkCanDownloadItemFile(authenticated: AuthenticatedUser, collectionLocId: string, itemId: string, hash: string): Promise { + private async checkCanDownloadItemFile(authenticated: AuthenticatedUser, collectionLocId: string, itemId: Hash, hash: Hash): Promise { const publishedCollectionItem = requireDefined(await this.logionNodeCollectionService.getCollectionItem({ collectionLocId, itemId @@ -368,10 +379,10 @@ export class CollectionController extends ApiController { } } - private async getCollectionItemWithFile(collectionLocId: string, itemId: string, hash: string): Promise { + private async getCollectionItemWithFile(collectionLocId: string, itemId: Hash, hash: Hash): Promise { const collectionItem = requireDefined( await this.collectionRepository.findBy(collectionLocId, itemId), - () => badRequest(`Collection item ${ collectionLocId }/${ itemId } not found in DB`)); + () => badRequest(`Collection item ${ collectionLocId }/${ itemId.toHex() } not found in DB`)); if (!collectionItem.hasFile(hash)) { throw badRequest("File does not exist"); } @@ -383,24 +394,30 @@ export class CollectionController extends ApiController { } static downloadCollectionFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/collection/{collectionLocId}/files/{hash}/{itemId}"].get!; + const operationObject = spec.paths["/api/collection/{collectionLocId}/files/{hashHex}/{itemIdHex}"].get!; operationObject.summary = "Downloads a copy of a file of the Collection LOC"; operationObject.description = "The authenticated user must be owner of the collection item"; operationObject.responses = getDefaultResponsesWithAnyBody(); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'hash': "The hash of the file to download", - 'itemId': "The ID of the collection item, used to validate that user is entitled to download file", + 'hashHex': "The hash of the file to download", + 'itemIdHex': "The ID of the collection item, used to validate that user is entitled to download file", }); } - @HttpGet('/:collectionLocId/files/:hash/:itemId') + @HttpGet('/:collectionLocId/files/:hashHex/:itemIdHex') @Async() @SendsResponse() - async downloadCollectionFile(_body: any, collectionLocId: string, hash: string, itemId: string): Promise { + async downloadCollectionFile(_body: any, collectionLocId: string, hashHex: string, itemIdHex: string): Promise { const authenticated = await this.authenticationService.authenticatedUser(this.request); + const hash = Hash.fromHex(hashHex); + const itemId = Hash.fromHex(itemIdHex); const file = await this.checkCanDownloadCollectionFile(authenticated, collectionLocId, hash, itemId); - const tempFilePath = CollectionController.tempFilePath({ collectionLocId, itemId, hash }); + const tempFilePath = CollectionController.tempFilePath({ + collectionLocId, + itemId, + hash, + }); await this.fileStorageService.exportFile(file, tempFilePath); const generatedOn = moment(); @@ -426,7 +443,7 @@ export class CollectionController extends ApiController { }); } - private async checkCanDownloadCollectionFile(authenticated: AuthenticatedUser, collectionLocId: string, hash: string, itemId: string): Promise { + private async checkCanDownloadCollectionFile(authenticated: AuthenticatedUser, collectionLocId: string, hash: Hash, itemId: Hash): Promise { const collection = requireDefined(await this.locRequestRepository.findById(collectionLocId)); if (!collection.hasFile(hash)) { throw badRequest("File does not exist"); @@ -449,7 +466,7 @@ export class CollectionController extends ApiController { } } - static tempFilePath(params: { collectionLocId: string, itemId: string, hash: string } ) { + static tempFilePath(params: { collectionLocId: string, itemId: Hash, hash: Hash } ) { const { collectionLocId, itemId, hash } = params return path.join(os.tmpdir(), `download-${ collectionLocId }-${ itemId }-${ hash }`) } @@ -469,7 +486,7 @@ export class CollectionController extends ApiController { @Async() async checkOwnership(_body: any, collectionLocId: string, itemId: string): Promise { const authenticated = await this.authenticationService.authenticatedUser(this.request); - const collectionItem = await this.collectionRepository.findBy(collectionLocId, itemId); + const collectionItem = await this.collectionRepository.findBy(collectionLocId, Hash.fromHex(itemId)); if(!collectionItem) { throw badRequest(`Collection item ${ collectionLocId } not found on-chain`); } @@ -497,7 +514,7 @@ export class CollectionController extends ApiController { await this.locRequestService.update(collectionLocId, async collection => { authenticated.require(user => user.is(collection.ownerAddress)); collection.setFileRestrictedDelivery({ - hash, + hash: Hash.fromHex(hash), restrictedDelivery: body.restrictedDelivery || false }); }); @@ -517,10 +534,14 @@ export class CollectionController extends ApiController { @HttpGet('/:collectionLocId/:itemId/latest-deliveries') @Async() async getLatestItemDeliveries(_body: any, collectionLocId: string, itemId: string): Promise { - return this.getItemDeliveries({ collectionLocId, itemId, limitPerFile: 1 }); + return this.getItemDeliveries({ + collectionLocId, + itemId: Hash.fromHex(itemId), + limitPerFile: 1, + }); } - private async getItemDeliveries(query: { collectionLocId: string, itemId: string, fileHash?: string, limitPerFile?: number }): Promise { + private async getItemDeliveries(query: { collectionLocId: string, itemId: Hash, fileHash?: Hash, limitPerFile?: number }): Promise { const { collectionLocId, itemId, fileHash, limitPerFile } = query; const item = await this.collectionRepository.findBy(collectionLocId, itemId); if(!item) { @@ -586,7 +607,10 @@ export class CollectionController extends ApiController { () => badRequest(`Collection ${ collectionLocId } not found`)); await this.authenticationService.authenticatedUserIsOneOf(this.request, collectionLoc.ownerAddress, collectionLoc.requesterAddress); - return this.getItemDeliveries({ collectionLocId, itemId }); + return this.getItemDeliveries({ + collectionLocId, + itemId: Hash.fromHex(itemId), + }); } static getAllCollectionFileDeliveries(spec: OpenAPIV3.Document) { @@ -607,11 +631,11 @@ export class CollectionController extends ApiController { () => badRequest(`Collection ${ collectionLocId } not found`)); await this.authenticationService.authenticatedUserIsOneOf(this.request, collectionLoc.ownerAddress, collectionLoc.requesterAddress); - const deliveries = await this._getAllCollectionFileDeliveries({ collectionLoc, hash }); + const deliveries = await this._getAllCollectionFileDeliveries({ collectionLoc, hash: Hash.fromHex(hash) }); return { deliveries }; } - private async _getAllCollectionFileDeliveries(query: { collectionLoc: LocRequestAggregateRoot, hash: string }): Promise { + private async _getAllCollectionFileDeliveries(query: { collectionLoc: LocRequestAggregateRoot, hash: Hash }): Promise { const { collectionLoc, hash } = query; if (!collectionLoc.hasFile(hash)) { throw badRequest("File not found") @@ -620,7 +644,7 @@ export class CollectionController extends ApiController { collectionLocId: collectionLoc.id!, hash }); - return delivered[hash].map(this.mapToResponse); + return delivered[hash.toHex()].map(this.mapToResponse); } static checkOneCollectionFileDelivery(spec: OpenAPIV3.Document) { @@ -639,7 +663,7 @@ export class CollectionController extends ApiController { @HttpPut('/:collectionLocId/file-deliveries') @Async() async checkOneCollectionFileDelivery(body: CheckCollectionDeliveryRequest, collectionLocId: string): Promise { - const deliveredFileHash = requireDefined(body.copyHash, () => badRequest("Missing attribute copyHash")) + const deliveredFileHash = Hash.fromHex(requireDefined(body.copyHash, () => badRequest("Missing attribute copyHash"))); const delivery = await this.locRequestRepository.findDeliveryByDeliveredFileHash({ collectionLocId, deliveredFileHash }) if (delivery === null) { throw badRequest("Provided copyHash is not from a delivered copy of a file from the collection") @@ -689,21 +713,21 @@ export class CollectionController extends ApiController { } static downloadFileSource(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemId}/files/{hash}/source"].get!; + const operationObject = spec.paths["/api/collection/{collectionLocId}/{itemIdHex}/files/{hashHex}/source"].get!; operationObject.summary = "Downloads the source of a file of the Collection Item"; operationObject.description = "The authenticated user must be the owner or the requester of the LOC"; operationObject.responses = getDefaultResponsesWithAnyBody(); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'itemId': "The ID of the Collection Item", - 'hash': "The hash of the file", + 'itemIdHex': "The ID of the Collection Item", + 'hashHex': "The hash of the file", }); } - @HttpGet('/:collectionLocId/:itemId/files/:hash/source') + @HttpGet('/:collectionLocId/:itemIdHex/files/:hashHex/source') @Async() @SendsResponse() - async downloadFileSource(_body: any, collectionLocId: string, itemId: string, hash: string): Promise { + async downloadFileSource(_body: any, collectionLocId: string, itemIdHex: string, hashHex: string): Promise { const authenticated = await this.authenticationService.authenticatedUser(this.request); const collectionLoc = requireDefined(await this.locRequestRepository.findById(collectionLocId), () => badRequest("Collection LOC not found")); @@ -712,6 +736,8 @@ export class CollectionController extends ApiController { requireDefined(collectionLoc.requesterAddress) ])); + const itemId = Hash.fromHex(itemIdHex); + const hash = Hash.fromHex(hashHex); const publishedCollectionItemFile = await this.getCollectionItemFile({ collectionLocId, itemId, diff --git a/src/logion/controllers/fileupload.ts b/src/logion/controllers/fileupload.ts index b7b44fb7..1faae42b 100644 --- a/src/logion/controllers/fileupload.ts +++ b/src/logion/controllers/fileupload.ts @@ -1,8 +1,9 @@ import { UploadedFile, FileArray } from "express-fileupload"; import { badRequest } from "@logion/rest-api-core"; import { sha256File } from "../lib/crypto/hashing.js"; +import { Hash } from "@logion/node-api"; -export async function getUploadedFile(request: Express.Request, receivedHash: string): Promise { +export async function getUploadedFile(request: Express.Request, receivedHash: Hash): Promise { const files: FileArray | undefined = request.files; if(files === undefined || files === null) { throw badRequest("No file detected"); @@ -18,8 +19,8 @@ export async function getUploadedFile(request: Express.Request, receivedHash: st throw badRequest("File upload failed (truncated)") } const localHash = await sha256File(file.tempFilePath); - if(localHash !== receivedHash) { - throw badRequest(`Received hash ${receivedHash} does not match ${localHash}`) + if(!localHash.equalTo(receivedHash)) { + throw badRequest(`Received hash ${ receivedHash.toHex() } does not match ${ localHash.toHex() }`) } file.name = Buffer.from(file.name, 'latin1').toString(); return file; diff --git a/src/logion/controllers/locrequest.controller.ts b/src/logion/controllers/locrequest.controller.ts index 98af9cb0..b18d652c 100644 --- a/src/logion/controllers/locrequest.controller.ts +++ b/src/logion/controllers/locrequest.controller.ts @@ -359,7 +359,7 @@ export class LocRequestController extends ApiController { createdOn: locDescription.createdOn || undefined, closedOn: request.closedOn || undefined, files: request.getFiles().map(file => ({ - hash: file.hash, + hash: file.hash.toHex(), nature: file.nature, addedOn: file.addedOn?.toISOString() || undefined, submitter: file.submitter, @@ -368,7 +368,7 @@ export class LocRequestController extends ApiController { })), metadata: request.getMetadataItems().map(item => ({ name: item.name, - nameHash: item.nameHash, + nameHash: item.nameHash.toHex(), value: item.value, addedOn: item.addedOn?.toISOString() || undefined, submitter: item.submitter, @@ -508,7 +508,7 @@ export class LocRequestController extends ApiController { const request = requireDefined(await this.locRequestRepository.findById(requestId)); const contributor = await this.locAuthorizationService.ensureContributor(this.request, request); - const hash = requireDefined(addFileView.hash, () => badRequest("No hash found for upload file")); + const hash = Hash.fromHex(requireDefined(addFileView.hash, () => badRequest("No hash found for upload file"))); if(request.hasFile(hash)) { throw new Error("File already present"); } @@ -553,7 +553,7 @@ export class LocRequestController extends ApiController { const request = requireDefined(await this.locRequestRepository.findById(requestId)); const { contributor, voter } = await this.ensureContributorOrVoter(request); - const file = request.getFile(hash); + const file = request.getFile(Hash.fromHex(hash)); if ( !accountEquals(contributor, request.getOwner()) && !accountEquals(contributor, request.getRequester()) && @@ -588,7 +588,7 @@ export class LocRequestController extends ApiController { let file: FileDescription | undefined; await this.locRequestService.update(requestId, async request => { const contributor = await this.locAuthorizationService.ensureContributor(this.request, request); - file = request.removeFile(contributor, hash); + file = request.removeFile(contributor, Hash.fromHex(hash)); }); if(file) { await this.fileStorageService.deleteFile(file); @@ -615,7 +615,7 @@ export class LocRequestController extends ApiController { throw badRequest("LOC must be OPEN for requesting item review"); } await this.locAuthorizationService.ensureContributor(this.request, request); - request.requestFileReview(hash); + request.requestFileReview(Hash.fromHex(hash)); }); const { userIdentity } = await this.locRequestAdapter.findUserPrivateData(request); @@ -625,7 +625,7 @@ export class LocRequestController extends ApiController { } static reviewFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/loc-request/{requestId}/files/{hash}/review"].post!; + const operationObject = spec.paths["/api/loc-request/{requestId}/files/{hashHex}/review"].post!; operationObject.summary = "Reviews the given file"; operationObject.description = "The authenticated user must be the owner of the LOC."; operationObject.requestBody = getRequestBody({ @@ -635,15 +635,16 @@ export class LocRequestController extends ApiController { operationObject.responses = getDefaultResponsesNoContent(); setPathParameters(operationObject, { 'requestId': "The ID of the LOC", - 'hash': "The hash of the file to review" + 'hashHex': "The hash of the file to review" }); } - @HttpPost('/:requestId/files/:hash/review') + @HttpPost('/:requestId/files/:hashHex/review') @Async() @SendsResponse() - async reviewFile(view: ReviewItemView, requestId: string, hash: string) { + async reviewFile(view: ReviewItemView, requestId: string, hashHex: string) { const authenticatedUser = await this.authenticationService.authenticatedUser(this.request); + const hash = Hash.fromHex(hashHex); const request = await this.locRequestService.update(requestId, async request => { authenticatedUser.require(user => user.is(request.ownerAddress)); if (view.decision === "ACCEPT") { @@ -661,20 +662,21 @@ export class LocRequestController extends ApiController { } static confirmFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/loc-request/{requestId}/files/{hash}/confirm"].put!; + const operationObject = spec.paths["/api/loc-request/{requestId}/files/{hashHex}/confirm"].put!; operationObject.summary = "Confirms a file of the LOC"; operationObject.description = "The authenticated user must be the owner of the LOC. Once a file is confirmed, it cannot be deleted anymore."; operationObject.responses = getDefaultResponsesNoContent(); setPathParameters(operationObject, { 'requestId': "The ID of the LOC", - 'hash': "The hash of the file to download" + 'hashHex': "The hash of the file to download" }); } - @HttpPut('/:requestId/files/:hash/confirm') + @HttpPut('/:requestId/files/:hashHex/confirm') @Async() @SendsResponse() - async confirmFile(_body: any, requestId: string, hash: string) { + async confirmFile(_body: any, requestId: string, hashHex: string) { + const hash = Hash.fromHex(hashHex); await this.locRequestService.update(requestId, async request => { const contributor = await this.locAuthorizationService.ensureContributor(this.request, request); const file = request.getFile(hash); @@ -705,7 +707,7 @@ export class LocRequestController extends ApiController { const authenticatedUser = await this.authenticationService.authenticatedUser(this.request); await this.locRequestService.update(requestId, async request => { authenticatedUser.require(user => user.is(request.ownerAddress)); - request.confirmFileAcknowledged(hash) + request.confirmFileAcknowledged(Hash.fromHex(hash)); }); this.response.sendStatus(204); } @@ -877,10 +879,10 @@ export class LocRequestController extends ApiController { @HttpDelete('/:requestId/metadata/:nameHash') @Async() - async deleteMetadata(_body: any, requestId: string, nameHash: Hash): Promise { + async deleteMetadata(_body: any, requestId: string, nameHash: string): Promise { await this.locRequestService.update(requestId, async request => { const contributor = await this.locAuthorizationService.ensureContributor(this.request, request); - request.removeMetadataItem(contributor, nameHash); + request.removeMetadataItem(contributor, Hash.fromHex(nameHash)); }); } @@ -898,19 +900,19 @@ export class LocRequestController extends ApiController { @HttpPost('/:requestId/metadata/:nameHash/review-request') @Async() @SendsResponse() - async requestMetadataReview(_body: any, requestId: string, nameHash: Hash) { + async requestMetadataReview(_body: any, requestId: string, nameHash: string) { await this.locRequestService.update(requestId, async request => { if (request.status !== 'OPEN') { throw badRequest("LOC must be OPEN for requesting item review"); } await this.locAuthorizationService.ensureContributor(this.request, request); - request.requestMetadataItemReview(nameHash); + request.requestMetadataItemReview(Hash.fromHex(nameHash)); }); this.response.sendStatus(204); } static reviewMetadata(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/loc-request/{requestId}/files/{hash}/review"].post!; + const operationObject = spec.paths["/api/loc-request/{requestId}/metadata/{nameHash}/review"].post!; operationObject.summary = "Reviews the given file"; operationObject.description = "The authenticated user must be the owner of the LOC."; operationObject.requestBody = getRequestBody({ @@ -927,15 +929,16 @@ export class LocRequestController extends ApiController { @HttpPost('/:requestId/metadata/:nameHash/review') @Async() @SendsResponse() - async reviewMetadata(view: ReviewItemView, requestId: string, nameHash: Hash) { + async reviewMetadata(view: ReviewItemView, requestId: string, nameHash: string) { const authenticatedUser = await this.authenticationService.authenticatedUser(this.request); + const hash = Hash.fromHex(nameHash); await this.locRequestService.update(requestId, async request => { authenticatedUser.require(user => user.is(request.ownerAddress)); if (view.decision === "ACCEPT") { - request.acceptMetadataItem(nameHash); + request.acceptMetadataItem(hash); } else { const reason = requireDefined(view.rejectReason, () => badRequest("Reason is required")); - request.rejectMetadataItem(nameHash, reason); + request.rejectMetadataItem(hash, reason); } }); this.response.sendStatus(204); @@ -955,12 +958,13 @@ export class LocRequestController extends ApiController { @HttpPut('/:requestId/metadata/:nameHash/confirm') @Async() @SendsResponse() - async confirmMetadata(_body: any, requestId: string, nameHash: Hash) { + async confirmMetadata(_body: any, requestId: string, nameHash: string) { await this.locRequestService.update(requestId, async request => { const contributor = await this.locAuthorizationService.ensureContributor(this.request, request); - const item = request.getMetadataItem(nameHash); + const hash = Hash.fromHex(nameHash); + const item = request.getMetadataItem(hash); if((item.submitter.type !== "Polkadot" && request.isOwner(contributor)) || accountEquals(item.submitter, contributor)) { - request.confirmMetadataItem(nameHash); + request.confirmMetadataItem(hash); } else { throw unauthorized("Contributor cannot confirm"); } @@ -982,11 +986,11 @@ export class LocRequestController extends ApiController { @HttpPut('/:requestId/metadata/:nameHash/confirm-acknowledged') @Async() @SendsResponse() - async confirmMetadataAcknowledged(_body: any, requestId: string, nameHash: Hash) { + async confirmMetadataAcknowledged(_body: any, requestId: string, nameHash: string) { const authenticatedUser = await this.authenticationService.authenticatedUser(this.request); await this.locRequestService.update(requestId, async request => { authenticatedUser.require(user => user.is(request.ownerAddress)); - request.confirmMetadataItemAcknowledged(nameHash) + request.confirmMetadataItemAcknowledged(Hash.fromHex(nameHash)); }); this.response.sendStatus(204); } @@ -1014,8 +1018,8 @@ export class LocRequestController extends ApiController { let description = `Statement of Facts for LOC ${ new UUID(locId).toDecimalString() }`; let linkNature = `Original LOC`; if (loc.locType === 'Collection') { - const itemId = requireDefined(createSofRequestView.itemId, - () => badRequest("Missing itemId")); + const itemId = Hash.fromHex(requireDefined(createSofRequestView.itemId, + () => badRequest("Missing itemId"))); requireDefined(await this.collectionRepository.findBy(locId, itemId), () => badRequest("Item not found")); description = `${ description } - ${ itemId }` diff --git a/src/logion/controllers/lofile.controller.ts b/src/logion/controllers/lofile.controller.ts index 09819c7b..a1d53e8d 100644 --- a/src/logion/controllers/lofile.controller.ts +++ b/src/logion/controllers/lofile.controller.ts @@ -19,6 +19,7 @@ import { getUploadedFile } from "./fileupload.js"; import { downloadAndClean } from "../lib/http.js"; import { components } from "./components.js"; import { LoFileService } from "../services/lofile.service.js"; +import { Hash } from "@logion/node-api"; type FileUploadData = components["schemas"]["FileUploadData"]; @@ -70,7 +71,7 @@ export class LoFileController extends ApiController { const authenticatedUser = await this.authenticationService.authenticatedUserIsLegalOfficerOnNode(this.request); authenticatedUser.require(user => user.is(legalOfficerAddress)); - const file = await getUploadedFile(this.request, requireDefined(body.hash, () => badRequest("No hash found for upload file"))); + const file = await getUploadedFile(this.request, Hash.fromHex(requireDefined(body.hash, () => badRequest("No hash found for upload file")))); const existingLoFile = await this.loFileRepository.findById({ id, legalOfficerAddress } ); if (existingLoFile) { const oidToRemove = existingLoFile.oid; diff --git a/src/logion/controllers/records.controller.ts b/src/logion/controllers/records.controller.ts index 7e3bbc7c..d905e983 100644 --- a/src/logion/controllers/records.controller.ts +++ b/src/logion/controllers/records.controller.ts @@ -1,4 +1,5 @@ import { AuthenticatedUser } from "@logion/authenticator"; +import { Hash } from "@logion/node-api"; import { injectable } from "inversify"; import { Controller, ApiController, Async, HttpGet, HttpPost, HttpPut, SendsResponse , HttpDelete} from "dinoloop"; import { @@ -111,11 +112,11 @@ export class TokensRecordController extends ApiController { const { collectionLocId, recordId, description, addedOn, files } = record; return { collectionLocId, - recordId, + recordId: recordId.toHex(), description, addedOn: addedOn?.toISOString(), files: files?.map(file => ({ - hash: file.hash, + hash: file.hash.toHex(), name: file.name, contentType: file.contentType, uploaded: file.cid !== undefined, @@ -124,19 +125,20 @@ export class TokensRecordController extends ApiController { } static getTokensRecord(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/record/{recordId}"].get!; + const operationObject = spec.paths["/api/records/{collectionLocId}/record/{recordIdHex}"].get!; operationObject.summary = "Gets the info of a published Collection Item"; operationObject.description = "No authentication required."; operationObject.responses = getPublicResponses("TokensRecordView"); setPathParameters(operationObject, { 'collectionLocId': "The id of the collection loc", - 'recordId': "The id of the collection item" + 'recordIdHex': "The id of the collection item" }); } - @HttpGet('/:collectionLocId/record/:recordId') + @HttpGet('/:collectionLocId/record/:recordIdHex') @Async() - async getTokensRecord(_body: any, collectionLocId: string, recordId: string): Promise { + async getTokensRecord(_body: any, collectionLocId: string, recordIdHex: string): Promise { + const recordId = Hash.fromHex(recordIdHex); requireDefined( await this.logionNodeTokensRecordService.getTokensRecord({ collectionLocId, recordId }), () => badRequest(`Tokens Record ${ collectionLocId }/${ recordId } not found`)); @@ -156,7 +158,7 @@ export class TokensRecordController extends ApiController { @HttpPost('/:collectionLocId/record') @Async() async submitItemPublicData(body: CreateTokensRecordView, collectionLocId: string): Promise { - const recordId = requireDefined(body.recordId); + const recordId = Hash.fromHex(requireDefined(body.recordId)); const existingItem = await this.tokensRecordRepository.findBy(collectionLocId, recordId); if(existingItem) { throw badRequest("Cannot replace existing item, you may try to cancel it first"); @@ -168,7 +170,7 @@ export class TokensRecordController extends ApiController { recordId, description, files: body.files?.map(file => ({ - hash: requireDefined(file.hash), + hash: Hash.fromHex(requireDefined(file.hash)), name: requireDefined(file.name), contentType: requireDefined(file.contentType), })), @@ -177,9 +179,10 @@ export class TokensRecordController extends ApiController { await this.tokensRecordService.addTokensRecord(record); } - @HttpDelete('/:collectionLocId/record/:recordId') + @HttpDelete('/:collectionLocId/record/:recordIdHex') @Async() - async cancelRecord(_body: any, collectionLocId: string, recordId: string): Promise { + async cancelRecord(_body: any, collectionLocId: string, recordIdHex: string): Promise { + const recordId = Hash.fromHex(recordIdHex); const publishedTokensRecord = await this.logionNodeTokensRecordService.getTokensRecord({ collectionLocId, recordId }); if (publishedTokensRecord) { throw badRequest("Tokens Record already published, cannot be cancelled"); @@ -187,14 +190,14 @@ export class TokensRecordController extends ApiController { const record = await this.tokensRecordRepository.findBy(collectionLocId, recordId); if(!record) { - throw badRequest(`Tokens Record ${ collectionLocId } ${ recordId } not found`); + throw badRequest(`Tokens Record ${ collectionLocId } ${ recordId.toHex() } not found`); } await this.tokensRecordService.cancelTokensRecord(record); } static uploadFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/{recordId}/files"].post!; + const operationObject = spec.paths["/api/records/{collectionLocId}/{recordIdHex}/files"].post!; operationObject.summary = "Adds a file to a Collection Item"; operationObject.description = "The authenticated user must be the requester of the LOC."; operationObject.responses = getDefaultResponsesNoContent(); @@ -204,24 +207,28 @@ export class TokensRecordController extends ApiController { }); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'recordId': "The ID of the Collection Item", + 'recordIdHex': "The ID of the Collection Item", }); } - @HttpPost('/:collectionLocId/:recordId/files') + @HttpPost('/:collectionLocId/:recordIdHex/files') @Async() - async uploadFile(body: FileUploadData, collectionLocId: string, recordId: string): Promise { + async uploadFile(body: FileUploadData, collectionLocId: string, recordIdHex: string): Promise { const collectionLoc = requireDefined(await this.locRequestRepository.findById(collectionLocId), () => badRequest(`Collection ${ collectionLocId } not found`)); await this.locAuthorizationService.ensureContributor(this.request, collectionLoc); - const publishedTokensRecord = await this.logionNodeTokensRecordService.getTokensRecord({ collectionLocId, recordId }) + const recordId = Hash.fromHex(recordIdHex); + const publishedTokensRecord = await this.logionNodeTokensRecordService.getTokensRecord({ + collectionLocId, + recordId + }); if (!publishedTokensRecord) { throw badRequest("Tokens Record not found on chain") } - const hash = requireDefined(body.hash, () => badRequest("No hash found for upload file")); + const hash = Hash.fromHex(requireDefined(body.hash, () => badRequest("No hash found for upload file"))); const file = await getUploadedFile(this.request, hash); const tokensRecordFile = await this.getTokensRecordFile({ @@ -238,7 +245,7 @@ export class TokensRecordController extends ApiController { const tokensRecord = await this.tokensRecordRepository.findBy(collectionLocId, recordId); if(!tokensRecord) { - throw badRequest(`Tokens Record ${ collectionLocId }/${ recordId } not found`); + throw badRequest(`Tokens Record ${ collectionLocId }/${ recordId.toHex() } not found`); } const recordFile = tokensRecord.file(hash); if (!recordFile) { @@ -259,7 +266,7 @@ export class TokensRecordController extends ApiController { if(!dbTokensRecord) { throw badRequest("Tokens Record not found"); } - const dbFile = dbTokensRecord.files?.find(file => file.hash === params.hash); + const dbFile = dbTokensRecord.files?.find(file => file.hash === params.hash.toHex()); const chainFile = await this.logionNodeTokensRecordService.getTokensRecordFile(params); if (dbFile && chainFile) { return { @@ -272,21 +279,25 @@ export class TokensRecordController extends ApiController { } static downloadItemFile(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/{recordId}/files/{hash}/{itemId}"].get!; + const operationObject = spec.paths["/api/records/{collectionLocId}/{recordIdHex}/files/{hashHex}/{itemIdHex}"].get!; operationObject.summary = "Downloads a copy of a file of the Collection Item"; operationObject.description = "The authenticated user must be the owner of the underlying token"; operationObject.responses = getDefaultResponsesWithAnyBody(); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'recordId': "The ID of the Collection Item", - 'hash': "The hash of the file", + 'recordIdHex': "The ID of the Tokens Record", + 'hashHex': "The hash of the file", + 'itemIdHex': "The ID of the Collection Item", }); } - @HttpGet('/:collectionLocId/:recordId/files/:hash/:itemId') + @HttpGet('/:collectionLocId/:recordIdHex/files/:hashHex/:itemIdHex') @Async() @SendsResponse() - async downloadItemFile(_body: any, collectionLocId: string, recordId: string, hash: string, itemId: string): Promise { + async downloadItemFile(_body: any, collectionLocId: string, recordIdHex: string, hashHex: string, itemIdHex: string): Promise { + const recordId = Hash.fromHex(recordIdHex); + const hash = Hash.fromHex(hashHex); + const itemId = Hash.fromHex(itemIdHex); const authenticated = await this.authenticationService.authenticatedUser(this.request); const collectionItem = await this.checkCanDownloadTokensRecordFile(authenticated, collectionLocId, recordId, hash, itemId); @@ -327,7 +338,7 @@ export class TokensRecordController extends ApiController { }); } - private async checkCanDownloadTokensRecordFile(authenticated: AuthenticatedUser, collectionLocId: string, recordId: string, hash: string, itemId: string): Promise { + private async checkCanDownloadTokensRecordFile(authenticated: AuthenticatedUser, collectionLocId: string, recordId: Hash, hash: Hash, itemId: Hash): Promise { const item = await this.collectionRepository.findBy(collectionLocId, itemId); if(!item) { throw badRequest(`Collection item ${ collectionLocId } not found on-chain`); @@ -343,10 +354,10 @@ export class TokensRecordController extends ApiController { } } - private async getTokensRecordWithFile(collectionLocId: string, recordId: string, hash: string): Promise { + private async getTokensRecordWithFile(collectionLocId: string, recordId: Hash, hash: Hash): Promise { const tokensRecord = requireDefined( await this.tokensRecordRepository.findBy(collectionLocId, recordId), - () => badRequest(`Tokens Record ${ collectionLocId }/${ recordId } not found in DB`)); + () => badRequest(`Tokens Record ${ collectionLocId }/${ recordId.toHex() } not found in DB`)); if (!tokensRecord.hasFile(hash)) { throw badRequest("File does not exist"); } @@ -357,12 +368,12 @@ export class TokensRecordController extends ApiController { return tokensRecord; } - static tempFilePath(params: { collectionLocId: string, recordId: string, hash: string } ) { - const { collectionLocId, recordId, hash } = params - return path.join(os.tmpdir(), `download-${ collectionLocId }-${ recordId }-${ hash }`) + static tempFilePath(params: { collectionLocId: string, recordId: Hash, hash: Hash } ) { + const { collectionLocId, recordId, hash } = params; + return path.join(os.tmpdir(), `download-${ collectionLocId }-${ recordId.toHex() }-${ hash.toHex() }`); } - private async getItemDeliveries(query: { collectionLocId: string, recordId: string, fileHash?: string, limitPerFile?: number }): Promise { + private async getItemDeliveries(query: { collectionLocId: string, recordId: Hash, fileHash?: Hash, limitPerFile?: number }): Promise { const { collectionLocId, recordId, fileHash, limitPerFile } = query; const delivered = await this.tokensRecordRepository.findLatestDeliveries({ collectionLocId, recordId, fileHash }); if(!delivered) { @@ -397,46 +408,49 @@ export class TokensRecordController extends ApiController { } static getAllItemDeliveries(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/{recordId}/deliveries"].get!; + const operationObject = spec.paths["/api/records/{collectionLocId}/{recordIdHex}/deliveries"].get!; operationObject.summary = "Provides information about all delivered copies of a collection file"; operationObject.description = "Only collection LOC owner is authorized"; operationObject.responses = getDefaultResponses("ItemDeliveriesResponse"); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'recordId': "The ID of the Collection Item", + 'recordIdHex': "The ID of the Collection Item", }); } - @HttpGet('/:collectionLocId/:recordId/deliveries') + @HttpGet('/:collectionLocId/:recordIdHex/deliveries') @Async() - async getAllItemDeliveries(_body: any, collectionLocId: string, recordId: string): Promise { + async getAllItemDeliveries(_body: any, collectionLocId: string, recordIdHex: string): Promise { const collectionLoc = requireDefined(await this.locRequestRepository.findById(collectionLocId), () => badRequest(`Collection ${ collectionLocId } not found`)); await this.locAuthorizationService.ensureContributor(this.request, collectionLoc); + const recordId = Hash.fromHex(recordIdHex); return this.getItemDeliveries({ collectionLocId, recordId }); } static downloadFileSource(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/{recordId}/files-sources/{hash}"].get!; + const operationObject = spec.paths["/api/records/{collectionLocId}/{recordIdHex}/files-sources/{hashHex}"].get!; operationObject.summary = "Downloads the source of a file of the Collection Item"; operationObject.description = "The authenticated user must be the owner or the requester of the LOC"; operationObject.responses = getDefaultResponsesWithAnyBody(); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'recordId': "The ID of the Collection Item", - 'hash': "The hash of the file", + 'recordIdHex': "The ID of the Collection Item", + 'hashHex': "The hash of the file", }); } - @HttpGet('/:collectionLocId/:recordId/files-sources/:hash') + @HttpGet('/:collectionLocId/:recordIdHex/files-sources/:hashHex') @Async() @SendsResponse() - async downloadFileSource(_body: any, collectionLocId: string, recordId: string, hash: string): Promise { + async downloadFileSource(_body: any, collectionLocId: string, recordIdHex: string, hashHex: string): Promise { const collectionLoc = requireDefined(await this.locRequestRepository.findById(collectionLocId), () => badRequest("Collection LOC not found")); await this.locAuthorizationService.ensureContributor(this.request, collectionLoc); + const recordId = Hash.fromHex(recordIdHex); + const hash = Hash.fromHex(hashHex); const tokensRecordFile = await this.getTokensRecordFile({ collectionLocId, recordId, @@ -460,7 +474,7 @@ export class TokensRecordController extends ApiController { } static checkOneFileDelivery(spec: OpenAPIV3.Document) { - const operationObject = spec.paths["/api/records/{collectionLocId}/{recordId}/deliveries/check"].put!; + const operationObject = spec.paths["/api/records/{collectionLocId}/{recordIdHex}/deliveries/check"].put!; operationObject.summary = "Provides information about one delivered collection file copy"; operationObject.description = "This is a public resource"; operationObject.requestBody = getRequestBody({ @@ -469,15 +483,20 @@ export class TokensRecordController extends ApiController { operationObject.responses = getPublicResponses("CheckCollectionDeliveryWitheOriginalResponse"); setPathParameters(operationObject, { 'collectionLocId': "The ID of the Collection LOC", - 'recordId': "The ID of the record", + 'recordIdHex': "The ID of the record", }); } - @HttpPut('/:collectionLocId/:recordId/deliveries/check') + @HttpPut('/:collectionLocId/:recordIdHex/deliveries/check') @Async() - async checkOneFileDelivery(body: CheckCollectionDeliveryRequest, collectionLocId: string, recordId: string): Promise { - const deliveredFileHash = requireDefined(body.copyHash, () => badRequest("Missing attribute copyHash")) - const delivery = await this.tokensRecordRepository.findDeliveryByDeliveredFileHash({ collectionLocId, recordId, deliveredFileHash }); + async checkOneFileDelivery(body: CheckCollectionDeliveryRequest, collectionLocId: string, recordIdHex: string): Promise { + const deliveredFileHash = Hash.fromHex(requireDefined(body.copyHash, () => badRequest("Missing attribute copyHash"))); + const recordId = Hash.fromHex(recordIdHex); + const delivery = await this.tokensRecordRepository.findDeliveryByDeliveredFileHash({ + collectionLocId, + recordId, + deliveredFileHash + }); if (delivery === null) { throw badRequest("Provided copyHash is not from a delivered copy of a file from the record"); } diff --git a/src/logion/lib/crypto/hashing.ts b/src/logion/lib/crypto/hashing.ts index 4e704bcd..3f7a794d 100644 --- a/src/logion/lib/crypto/hashing.ts +++ b/src/logion/lib/crypto/hashing.ts @@ -16,11 +16,11 @@ export class HashTransformer implements ValueTransformer { if (!value) { return undefined; } - return `0x${ value.toString('hex') }`; + return new Hash(value); } to(value: Hash): Buffer { - return Buffer.from(value.substring(2), "hex"); + return Buffer.from(value.bytes); } } @@ -28,10 +28,6 @@ export function sha256(attributes: any[]): string { return hash(algorithm, attributes); } -export function sha256String(message: string): Hash { - return `0x${ hash(algorithm, [ message ], "hex") }`; -} - function hash(algorithm: string, attributes: any[], encoding: BinaryToTextEncoding = "base64"): string { const hash = crypto.createHash(algorithm); attributes.forEach(attribute => hash.update(Buffer.from(attribute.toString(), 'utf8'))); @@ -48,7 +44,7 @@ export function sha256File(fileName: string): Promise { } const promise = new Promise((success, error) => { stream.on('end', function () { - success(`0x${ hash.digest('hex') }`); + success(Hash.fromHex(`0x${ hash.digest('hex') }`)); }); stream.on('error', error); }); diff --git a/src/logion/model/collection.model.ts b/src/logion/model/collection.model.ts index 4fed864d..a3253874 100644 --- a/src/logion/model/collection.model.ts +++ b/src/logion/model/collection.model.ts @@ -15,10 +15,12 @@ import { injectable } from "inversify"; import { appDataSource, requireDefined } from "@logion/rest-api-core"; import { Child, saveChildren, saveIndexedChildren } from "./child.js"; import { HasIndex } from "../lib/db/collections.js"; +import { Hash } from "@logion/node-api"; +import { HexString } from "@polkadot/util/types"; export interface CollectionItemDescription { readonly collectionLocId: string - readonly itemId: string + readonly itemId: Hash readonly addedOn?: Moment readonly files?: CollectionItemFileDescription[] readonly description?: string @@ -29,7 +31,7 @@ export interface CollectionItemDescription { export interface CollectionItemFileDescription { readonly name?: string; readonly contentType?: string; - readonly hash: string; + readonly hash: Hash; readonly cid?: string; } @@ -78,7 +80,7 @@ export class CollectionItemAggregateRoot { getDescription(): CollectionItemDescription { return { collectionLocId: this.collectionLocId!, - itemId: this.itemId!, + itemId: Hash.fromHex(this.itemId!), addedOn: this.addedOn ? moment(this.addedOn) : undefined, description: this.description, token: this.token ? this.token.getDescription() : undefined, @@ -87,7 +89,7 @@ export class CollectionItemAggregateRoot { } } - setFileCid(fileDescription: { hash: string, cid: string }) { + setFileCid(fileDescription: { hash: Hash, cid: string }) { const { hash, cid } = fileDescription; const file = this.file(hash); if(!file) { @@ -125,15 +127,15 @@ export class CollectionItemAggregateRoot { @Column("timestamp without time zone", { name: "added_on", nullable: true }) addedOn?: Date; - hasFile(hash: string): boolean { + hasFile(hash: Hash): boolean { return this.file(hash) !== undefined; } - file(hash: string): CollectionItemFile | undefined { - return this.files!.find(file => file.hash === hash) + file(hash: Hash): CollectionItemFile | undefined { + return this.files!.find(file => file.hash === hash.toHex()); } - getFile(hash: string): CollectionItemFile { + getFile(hash: Hash): CollectionItemFile { return this.file(hash)!; } @@ -155,7 +157,7 @@ export class CollectionItemFile extends Child { const file = new CollectionItemFile(); file.name = description.name; file.contentType = description.contentType; - file.hash = description.hash; + file.hash = description.hash.toHex(); if(root) { file.collectionLocId = root.collectionLocId; @@ -169,7 +171,7 @@ export class CollectionItemFile extends Child { getDescription(): CollectionItemFileDescription { return { - hash: this.hash!, + hash: Hash.fromHex(this.hash as HexString), name: this.name, contentType: this.contentType, cid: this.cid || undefined, @@ -210,7 +212,7 @@ export class CollectionItemFile extends Child { collectionItem?: CollectionItemAggregateRoot; addDeliveredFile(params: { - deliveredFileHash: string, + deliveredFileHash: Hash, generatedOn: Moment, owner: string, }): CollectionItemFileDelivered { @@ -221,7 +223,7 @@ export class CollectionItemFile extends Child { deliveredFile.itemId = this.itemId; deliveredFile.hash = this.hash; - deliveredFile.deliveredFileHash = deliveredFileHash; + deliveredFile.deliveredFileHash = deliveredFileHash.toHex(); deliveredFile.generatedOn = generatedOn.toDate(); deliveredFile.owner = owner; @@ -383,8 +385,8 @@ export class CollectionRepository { } } - public async findBy(collectionLocId: string, itemId: string): Promise { - return this.repository.findOneBy({ collectionLocId, itemId }) + public async findBy(collectionLocId: string, itemId: Hash): Promise { + return this.repository.findOneBy({ collectionLocId, itemId: itemId.toHex() }); } public async findAllBy(collectionLocId: string): Promise { @@ -396,10 +398,10 @@ export class CollectionRepository { return builder.getMany(); } - public async findLatestDelivery(query: { collectionLocId: string, itemId: string, fileHash: string }): Promise { + public async findLatestDelivery(query: { collectionLocId: string, itemId: Hash, fileHash: Hash }): Promise { const { collectionLocId, itemId, fileHash } = query; const deliveries = await this.findLatestDeliveries({ collectionLocId, itemId, fileHash, limit: 1 }); - const deliveriesList = deliveries[fileHash]; + const deliveriesList = deliveries[fileHash.toHex()]; if(deliveriesList) { return deliveriesList[0]; } else { @@ -407,13 +409,13 @@ export class CollectionRepository { } } - public async findLatestDeliveries(query: { collectionLocId: string, itemId: string, fileHash?: string, limit?: number }): Promise> { + public async findLatestDeliveries(query: { collectionLocId: string, itemId: Hash, fileHash?: Hash, limit?: number }): Promise> { const { collectionLocId, itemId, fileHash, limit } = query; let builder = this.deliveredRepository.createQueryBuilder("delivery"); builder.where("delivery.collection_loc_id = :collectionLocId", { collectionLocId }); - builder.andWhere("delivery.item_id = :itemId", { itemId }); + builder.andWhere("delivery.item_id = :itemId", { itemId: itemId.toHex() }); if(fileHash) { - builder.andWhere("delivery.hash = :fileHash", { fileHash }); + builder.andWhere("delivery.hash = :fileHash", { fileHash: fileHash.toHex() }); } builder.orderBy("delivery.generated_on", "DESC"); if(limit) { @@ -452,7 +454,7 @@ export class CollectionFactory { const { collectionLocId, itemId } = params; const item = new CollectionItemAggregateRoot() item.collectionLocId = collectionLocId; - item.itemId = itemId; + item.itemId = itemId.toHex(); item.description = params.description; item.token = CollectionItemToken.from(params.token); item.files = params.files?.map(file => CollectionItemFile.from(file, item)) || []; diff --git a/src/logion/model/locrequest.model.ts b/src/logion/model/locrequest.model.ts index cb0a3e93..3997c1a1 100644 --- a/src/logion/model/locrequest.model.ts +++ b/src/logion/model/locrequest.model.ts @@ -26,7 +26,7 @@ import { polkadotAccount } from "./supportedaccountid.model.js"; import { SelectQueryBuilder } from "typeorm/query-builder/SelectQueryBuilder.js"; -import { sha256String, Hash, HashTransformer } from "../lib/crypto/hashing.js"; +import { Hash, HashTransformer } from "../lib/crypto/hashing.js"; const { logger } = Log; @@ -57,7 +57,7 @@ export interface LocRequestDecision { export interface FileParams { readonly name: string; - readonly hash: string; + readonly hash: Hash; readonly oid?: number; readonly cid?: string; readonly contentType: string; @@ -211,7 +211,7 @@ class EmbeddableSeal { return undefined; } const result = new EmbeddableSeal(); - result.hash = seal.hash; + result.hash = seal.hash.toHex(); result.salt = seal.salt; result.version = seal.version; return result; @@ -222,7 +222,7 @@ function toPublicSeal(embedded: EmbeddableSeal | undefined): PublicSeal | undefi return embedded && embedded.hash && embedded.version !== undefined && embedded.version !== null ? { - hash: embedded.hash, + hash: Hash.fromHex(embedded.hash), version: embedded.version } : undefined; @@ -351,7 +351,7 @@ export class LocRequestAggregateRoot { file.requestId = this.id; file.index = this.files!.length; file.name = fileDescription.name; - file.hash! = fileDescription.hash; + file.hash! = fileDescription.hash.toHex(); file.cid = fileDescription.cid; file.contentType = fileDescription.contentType; file.lifecycle = EmbeddableLifecycle.from(alreadyReviewed); @@ -369,11 +369,11 @@ export class LocRequestAggregateRoot { } } - requestFileReview(hash: string) { + requestFileReview(hash: Hash) { this.mutateFile(hash, item => item.lifecycle!.requestReview()); } - acceptFile(hash: string) { + acceptFile(hash: Hash) { this.ensureAcceptedOrOpen(); this.mutateFile(hash, item => item.lifecycle!.accept()); } @@ -384,11 +384,11 @@ export class LocRequestAggregateRoot { } } - rejectFile(hash: string, reason: string) { + rejectFile(hash: Hash, reason: string) { this.mutateFile(hash, item => item.lifecycle!.reject(reason)); } - confirmFile(hash: string) { + confirmFile(hash: Hash) { this.mutateFile(hash, item => item.lifecycle!.confirm(item.submitter?.type !== "Polkadot" || this.isOwner(item.submitter.toSupportedAccountId()))); } @@ -396,11 +396,11 @@ export class LocRequestAggregateRoot { return accountEquals(account, { address: this.ownerAddress, type: "Polkadot" }); } - confirmFileAcknowledged(hash: string, acknowledgedOn?: Moment) { + confirmFileAcknowledged(hash: Hash, acknowledgedOn?: Moment) { this.mutateFile(hash, item => item.lifecycle!.confirmAcknowledged(acknowledgedOn)); } - private mutateFile(hash: string, mutator: (item: LocFile) => void) { + private mutateFile(hash: Hash, mutator: (item: LocFile) => void) { const file = this.getFileOrThrow(hash); mutator(file); file._toUpdate = true; @@ -411,23 +411,23 @@ export class LocRequestAggregateRoot { this.files?.forEach(item => item.status = statusTo); } - private getFileOrThrow(hash: string) { + private getFileOrThrow(hash: Hash) { const file = this.file(hash); if(!file) { - throw new Error(`No file with hash ${hash}`); + throw new Error(`No file with hash ${hash.toHex()}`); } return file; } - private file(hash: string): LocFile | undefined { - return this.files?.find(file => file.hash === hash); + private file(hash: Hash): LocFile | undefined { + return this.files?.find(file => file.hash === hash.toHex()); } - hasFile(hash: string): boolean { + hasFile(hash: Hash): boolean { return this.file(hash) !== undefined; } - getFile(hash: string): FileDescription { + getFile(hash: Hash): FileDescription { return this.toFileDescription(this.getFileOrThrow(hash)); } @@ -435,7 +435,7 @@ export class LocRequestAggregateRoot { return { name: file!.name!, contentType: file!.contentType!, - hash: file!.hash!, + hash: Hash.fromHex(file!.hash!), oid: file!.oid, cid: file!.cid, nature: file!.nature!, @@ -493,7 +493,7 @@ export class LocRequestAggregateRoot { addMetadataItem(itemDescription: MetadataItemParams, alreadyReviewed: boolean) { this.ensureEditable(); - const nameHash = sha256String(itemDescription.name); + const nameHash = Hash.of(itemDescription.name); if (this.hasMetadataItem(nameHash)) { throw new Error("A metadata item with given nameHash was already added to this LOC"); } @@ -541,7 +541,7 @@ export class LocRequestAggregateRoot { removeMetadataItem(remover: SupportedAccountId, nameHash: Hash): void { this.ensureEditable(); - const removedItemIndex: number = this.metadata!.findIndex(link => link.nameHash === nameHash); + const removedItemIndex: number = this.metadata!.findIndex(link => link.nameHash?.equalTo(nameHash)); if (removedItemIndex === -1) { throw new Error("No metadata item with given name"); } @@ -599,10 +599,10 @@ export class LocRequestAggregateRoot { } metadataItem(nameHash: Hash): LocMetadataItem | undefined { - return this.metadata!.find(metadataItem => metadataItem.nameHash === nameHash) + return this.metadata!.find(metadataItem => metadataItem.nameHash?.equalTo(nameHash)) } - setFileAddedOn(hash: string, addedOn: Moment) { + setFileAddedOn(hash: Hash, addedOn: Moment) { const file = this.file(hash); if (!file) { logger.error(`File with hash ${ hash } not found`); @@ -612,9 +612,9 @@ export class LocRequestAggregateRoot { file._toUpdate = true; } - removeFile(removerAddress: SupportedAccountId, hash: string): FileDescription { + removeFile(removerAddress: SupportedAccountId, hash: Hash): FileDescription { this.ensureEditable(); - const removedFileIndex: number = this.files!.findIndex(file => file.hash === hash); + const removedFileIndex: number = this.files!.findIndex(file => file.hash === hash.toHex()); if (removedFileIndex === -1) { throw new Error("No file with given hash"); } @@ -794,8 +794,8 @@ export class LocRequestAggregateRoot { } addDeliveredFile(params: { - hash: string, - deliveredFileHash: string, + hash: Hash, + deliveredFileHash: Hash, generatedOn: Moment, owner: string, }): void { @@ -815,7 +815,7 @@ export class LocRequestAggregateRoot { } setFileRestrictedDelivery(params: { - hash: string, + hash: Hash, restrictedDelivery: boolean, }): void { if(this.locType !== 'Collection') { @@ -826,7 +826,7 @@ export class LocRequestAggregateRoot { file.setRestrictedDelivery(restrictedDelivery); } - setFileFees(hash: string, fees: Fees, storageFeePaidBy: string | undefined) { + setFileFees(hash: Hash, fees: Fees, storageFeePaidBy: string | undefined) { const file = this.getFileOrThrow(hash); file.setFees(fees, storageFeePaidBy); } @@ -1020,7 +1020,7 @@ export class LocFile extends Child implements HasIndex, Submitted { } addDeliveredFile(params: { - deliveredFileHash: string, + deliveredFileHash: Hash, generatedOn: Moment, owner: string, }): LocFileDelivered { @@ -1030,7 +1030,7 @@ export class LocFile extends Child implements HasIndex, Submitted { deliveredFile.requestId = this.requestId; deliveredFile.hash = this.hash; - deliveredFile.deliveredFileHash = deliveredFileHash; + deliveredFile.deliveredFileHash = deliveredFileHash.toHex(); deliveredFile.generatedOn = generatedOn.toDate(); deliveredFile.owner = owner; @@ -1348,12 +1348,12 @@ export class LocRequestRepository { await this.repository.manager.delete(LocRequestAggregateRoot, request.id); } - public async findAllDeliveries(query: { collectionLocId: string, hash?: string }): Promise> { + public async findAllDeliveries(query: { collectionLocId: string, hash?: Hash }): Promise> { const { collectionLocId, hash } = query; let builder = this.deliveredRepository.createQueryBuilder("delivery"); builder.where("delivery.request_id = :collectionLocId", { collectionLocId }); if (hash) { - builder.andWhere("delivery.hash = :hash", { hash }); + builder.andWhere("delivery.hash = :hash", { hash: hash.toHex() }); } builder.orderBy("delivery.generated_on", "DESC"); const deliveriesList = await builder.getMany(); @@ -1367,10 +1367,10 @@ export class LocRequestRepository { return deliveries; } - public async findDeliveryByDeliveredFileHash(query: { collectionLocId: string, deliveredFileHash: string }): Promise { + public async findDeliveryByDeliveredFileHash(query: { collectionLocId: string, deliveredFileHash: Hash }): Promise { const requestId = query.collectionLocId; const { deliveredFileHash } = query; - return await this.deliveredRepository.findOneBy({ requestId, deliveredFileHash }) + return await this.deliveredRepository.findOneBy({ requestId, deliveredFileHash: deliveredFileHash.toHex() }) } } diff --git a/src/logion/model/tokensrecord.model.ts b/src/logion/model/tokensrecord.model.ts index adddb110..b17020ba 100644 --- a/src/logion/model/tokensrecord.model.ts +++ b/src/logion/model/tokensrecord.model.ts @@ -14,10 +14,11 @@ import { injectable } from "inversify"; import { appDataSource, requireDefined } from "@logion/rest-api-core"; import { Child, saveChildren } from "./child.js"; +import { Hash } from "@logion/node-api"; export interface TokensRecordDescription { readonly collectionLocId: string; - readonly recordId: string; + readonly recordId: Hash; readonly description?: string; readonly addedOn?: Moment; readonly files?: TokensRecordFileDescription[]; @@ -26,7 +27,7 @@ export interface TokensRecordDescription { export interface TokensRecordFileDescription { readonly name?: string; readonly contentType?: string; - readonly hash: string; + readonly hash: Hash; readonly cid?: string; } @@ -36,14 +37,14 @@ export class TokensRecordAggregateRoot { getDescription(): TokensRecordDescription { return { collectionLocId: this.collectionLocId!, - recordId: this.recordId!, + recordId: Hash.fromHex(this.recordId!), description: this.description, addedOn: moment(this.addedOn), files: this.files?.map(file => file.getDescription()) || [] } } - setFileCid(fileDescription: { hash: string, cid: string }) { + setFileCid(fileDescription: { hash: Hash, cid: string }) { const { hash, cid } = fileDescription; const file = this.file(hash); if(!file) { @@ -75,15 +76,15 @@ export class TokensRecordAggregateRoot { @Column("timestamp without time zone", { name: "added_on", nullable: true }) addedOn?: Date; - hasFile(hash: string): boolean { + hasFile(hash: Hash): boolean { return this.file(hash) !== undefined; } - file(hash: string): TokensRecordFile | undefined { - return this.files!.find(file => file.hash === hash) + file(hash: Hash): TokensRecordFile | undefined { + return this.files!.find(file => file.hash === hash.toHex()); } - getFile(hash: string): TokensRecordFile { + getFile(hash: Hash): TokensRecordFile { return this.file(hash)!; } @@ -101,7 +102,7 @@ export class TokensRecordFile extends Child { const file = new TokensRecordFile(); file.name = description.name; file.contentType = description.contentType; - file.hash = description.hash; + file.hash = description.hash.toHex(); if(root) { file.collectionLocId = root.collectionLocId; @@ -115,7 +116,7 @@ export class TokensRecordFile extends Child { getDescription(): TokensRecordFileDescription { return { - hash: this.hash!, + hash: Hash.fromHex(this.hash!), name: this.name, contentType: this.contentType, cid: this.cid || undefined, @@ -156,7 +157,7 @@ export class TokensRecordFile extends Child { tokenRecord?: TokensRecordAggregateRoot; addDeliveredFile(params: { - deliveredFileHash: string, + deliveredFileHash: Hash, generatedOn: Moment, owner: string, }): TokensRecordFileDelivered { @@ -167,7 +168,7 @@ export class TokensRecordFile extends Child { deliveredFile.recordId = this.recordId; deliveredFile.hash = this.hash; - deliveredFile.deliveredFileHash = deliveredFileHash; + deliveredFile.deliveredFileHash = deliveredFileHash.toHex(); deliveredFile.generatedOn = generatedOn.toDate(); deliveredFile.owner = owner; @@ -267,8 +268,8 @@ export class TokensRecordRepository { }); } - public async findBy(collectionLocId: string, recordId: string): Promise { - return this.repository.findOneBy({ collectionLocId, recordId }) + public async findBy(collectionLocId: string, recordId: Hash): Promise { + return this.repository.findOneBy({ collectionLocId, recordId: recordId.toHex() }); } public async findAllBy(collectionLocId: string): Promise { @@ -279,10 +280,10 @@ export class TokensRecordRepository { return builder.getMany(); } - public async findLatestDelivery(query: { collectionLocId: string, recordId: string, fileHash: string }): Promise { + public async findLatestDelivery(query: { collectionLocId: string, recordId: Hash, fileHash: Hash }): Promise { const { collectionLocId, recordId, fileHash } = query; const deliveries = await this.findLatestDeliveries({ collectionLocId, recordId, fileHash, limit: 1 }); - const deliveriesList = deliveries[fileHash]; + const deliveriesList = deliveries[fileHash.toHex()]; if(deliveriesList) { return deliveriesList[0]; } else { @@ -290,13 +291,13 @@ export class TokensRecordRepository { } } - public async findLatestDeliveries(query: { collectionLocId: string, recordId: string, fileHash?: string, limit?: number }): Promise> { + public async findLatestDeliveries(query: { collectionLocId: string, recordId: Hash, fileHash?: Hash, limit?: number }): Promise> { const { collectionLocId, recordId, fileHash, limit } = query; let builder = this.deliveredRepository.createQueryBuilder("delivery"); builder.where("delivery.collection_loc_id = :collectionLocId", { collectionLocId }); - builder.andWhere("delivery.record_id = :recordId", { recordId }); + builder.andWhere("delivery.record_id = :recordId", { recordId: recordId.toHex() }); if(fileHash) { - builder.andWhere("delivery.hash = :fileHash", { fileHash }); + builder.andWhere("delivery.hash = :fileHash", { fileHash: fileHash.toHex() }); } builder.orderBy("delivery.generated_on", "DESC"); if(limit) { @@ -313,9 +314,13 @@ export class TokensRecordRepository { return deliveries; } - public async findDeliveryByDeliveredFileHash(query: { collectionLocId: string, recordId: string, deliveredFileHash: string }): Promise { + public async findDeliveryByDeliveredFileHash(query: { collectionLocId: string, recordId: Hash, deliveredFileHash: Hash }): Promise { const { collectionLocId, recordId, deliveredFileHash } = query; - return await this.deliveredRepository.findOneBy({ collectionLocId, recordId, deliveredFileHash }); + return await this.deliveredRepository.findOneBy({ + collectionLocId, + recordId: recordId.toHex(), + deliveredFileHash: deliveredFileHash.toHex(), + }); } async delete(item: TokensRecordAggregateRoot): Promise { @@ -339,7 +344,7 @@ export class TokensRecordFactory { const { collectionLocId, recordId } = params; const item = new TokensRecordAggregateRoot() item.collectionLocId = collectionLocId; - item.recordId = recordId; + item.recordId = recordId.toHex(); item.description = params.description; item.files = params.files?.map(file => TokensRecordFile.from(file, item)) || []; return item; diff --git a/src/logion/services/collection.service.ts b/src/logion/services/collection.service.ts index cc4945cc..c5d9929f 100644 --- a/src/logion/services/collection.service.ts +++ b/src/logion/services/collection.service.ts @@ -5,11 +5,11 @@ import { CollectionItemAggregateRoot, CollectionRepository } from "../model/coll export interface GetCollectionItemParams { collectionLocId: string, - itemId: string, + itemId: Hash, } export interface GetCollectionItemFileParams extends GetCollectionItemParams { - hash: string + hash: Hash; } @injectable() @@ -24,14 +24,14 @@ export class LogionNodeCollectionService { const api = await this.polkadotService.readyApi(); return await api.queries.getCollectionItem( new UUID(collectionLocId), - itemId as Hash, + itemId, ); } async getCollectionItemFile(params: GetCollectionItemFileParams): Promise { const { hash } = params; const collectionItem = await this.getCollectionItem(params); - return collectionItem?.files.find(itemFile => itemFile.hash === hash); + return collectionItem?.files.find(itemFile => itemFile.hash.equalTo(hash)); } } @@ -44,7 +44,7 @@ export abstract class CollectionService { async addCollectionItem(item: CollectionItemAggregateRoot): Promise { const previousItem = await this.collectionRepository.findBy( requireDefined(item.collectionLocId), - requireDefined(item.itemId), + Hash.fromHex(requireDefined(item.itemId)), ); if(previousItem) { throw new Error("Cannot replace existing item"); @@ -56,7 +56,7 @@ export abstract class CollectionService { await this.collectionRepository.delete(item); } - async update(collectionLocId: string, itemId: string, mutator: (item: CollectionItemAggregateRoot) => Promise): Promise { + async update(collectionLocId: string, itemId: Hash, mutator: (item: CollectionItemAggregateRoot) => Promise): Promise { const item = requireDefined(await this.collectionRepository.findBy(collectionLocId, itemId)); await mutator(item); await this.collectionRepository.save(item); @@ -84,7 +84,7 @@ export class TransactionalCollectionService extends CollectionService { } @DefaultTransactional() - async update(collectionLocId: string, itemId: string, mutator: (item: CollectionItemAggregateRoot) => Promise): Promise { + async update(collectionLocId: string, itemId: Hash, mutator: (item: CollectionItemAggregateRoot) => Promise): Promise { return super.update(collectionLocId, itemId, mutator); } } diff --git a/src/logion/services/idenfy/idenfy.service.ts b/src/logion/services/idenfy/idenfy.service.ts index 1acf9085..bea83083 100644 --- a/src/logion/services/idenfy/idenfy.service.ts +++ b/src/logion/services/idenfy/idenfy.service.ts @@ -1,4 +1,5 @@ import { Log, requireDefined } from "@logion/rest-api-core"; +import { Hash } from "@logion/node-api"; import { AxiosError, AxiosInstance } from "axios"; import { injectable } from "inversify"; import { DateTime } from "luxon"; @@ -27,12 +28,11 @@ export interface IdenfyVerificationCreation { } export interface IdenfyVerificationRedirect { - url: string; } interface IPFSFile { - hash: string; + hash: Hash; cid: string; size: number; } diff --git a/src/logion/services/locsynchronization.service.ts b/src/logion/services/locsynchronization.service.ts index 06bf1701..d086094e 100644 --- a/src/logion/services/locsynchronization.service.ts +++ b/src/logion/services/locsynchronization.service.ts @@ -1,5 +1,5 @@ import { injectable } from 'inversify'; -import { UUID, Adapters, Fees } from "@logion/node-api"; +import { UUID, Adapters, Fees, Hash } from "@logion/node-api"; import { Log, PolkadotService, requireDefined } from "@logion/rest-api-core"; import { Moment } from "moment"; @@ -136,7 +136,7 @@ export class LocSynchronizer { } private async updateMetadataItem(loc: LocRequestAggregateRoot, timestamp: Moment, extrinsic: JsonExtrinsic) { - const nameHash = Adapters.asHexString(Adapters.asJsonObject(extrinsic.call.args['item']).name); + const nameHash = Hash.fromHex(Adapters.asHexString(Adapters.asJsonObject(extrinsic.call.args['item']).name)); loc.setMetadataItemAddedOn(nameHash, timestamp); const inclusionFee = await extrinsic.partialFee(); if(inclusionFee) { @@ -162,12 +162,12 @@ export class LocSynchronizer { } } - private getFileHash(extrinsic: JsonExtrinsic): string { + private getFileHash(extrinsic: JsonExtrinsic): Hash { const file = Adapters.asJsonObject(extrinsic.call.args['file']); if("hash_" in file && Adapters.isHexString(file.hash_)) { - return Adapters.asHexString(file.hash_); + return Hash.fromHex(Adapters.asHexString(file.hash_)); } else if("hash" in file && Adapters.isHexString(file.hash)) { - return Adapters.asHexString(file.hash); + return Hash.fromHex(Adapters.asHexString(file.hash)); } else { throw new Error("File has no hash"); } @@ -187,7 +187,7 @@ export class LocSynchronizer { private async addCollectionItem(timestamp: Moment, extrinsic: JsonExtrinsic) { const collectionLocId = extractUuid('collection_loc_id', extrinsic.call.args); - const itemId = Adapters.asHexString(extrinsic.call.args['item_id']); + const itemId = Hash.fromHex(Adapters.asHexString(extrinsic.call.args['item_id'])); const loc = await this.locRequestRepository.findById(collectionLocId); if (loc !== null) { logger.info("Confirming Collection Item %s to LOC %s", itemId, collectionLocId); @@ -302,7 +302,7 @@ export class LocSynchronizer { } private async addTokensRecord(collectionLocId: string, timestamp: Moment, extrinsic: JsonExtrinsic) { - const recordId = Adapters.asHexString(extrinsic.call.args['record_id']); + const recordId = Hash.fromHex(Adapters.asHexString(extrinsic.call.args['record_id'])); const loc = await this.locRequestRepository.findById(collectionLocId); if (loc !== null) { logger.info("Confirming Tokens Record %s to LOC %s", recordId, collectionLocId); @@ -314,13 +314,13 @@ export class LocSynchronizer { private async confirmAcknowledgedFile(timestamp: Moment, extrinsic: JsonExtrinsic) { const locId = extractUuid('loc_id', extrinsic.call.args); - const hash = Adapters.asHexString(extrinsic.call.args['hash']); + const hash = Hash.fromHex(Adapters.asHexString(extrinsic.call.args['hash'])); await this.mutateLoc(locId, async loc => loc.confirmFileAcknowledged(hash, timestamp)); } private async confirmAcknowledgedMetadata(timestamp: Moment, extrinsic: JsonExtrinsic) { const locId = extractUuid('loc_id', extrinsic.call.args); - const nameHash = Adapters.asHexString(extrinsic.call.args['name']); + const nameHash = Hash.fromHex(Adapters.asHexString(extrinsic.call.args['name'])); await this.mutateLoc(locId, async loc => loc.confirmMetadataItemAcknowledged(nameHash, timestamp)); } } diff --git a/src/logion/services/seal.service.ts b/src/logion/services/seal.service.ts index 3565e066..e0a5b1d8 100644 --- a/src/logion/services/seal.service.ts +++ b/src/logion/services/seal.service.ts @@ -1,5 +1,5 @@ import { injectable } from "inversify"; -import { sha256String } from "../lib/crypto/hashing.js"; +import { Hash } from "@logion/node-api"; import { v4 as uuid } from "uuid"; import { UserIdentity } from "../model/useridentity.js"; import { PostalAddress } from "../model/postaladdress.js"; @@ -8,7 +8,7 @@ import { PersonalInfo } from "../model/personalinfo.model.js"; const SEPARATOR: string = "-"; export interface PublicSeal { - hash: string; + hash: Hash; version: number; } @@ -29,7 +29,7 @@ abstract class SealService { } protected _seal(values: string[], salt: string, version: number): Seal { - const hash = sha256String(this.serialize(values, salt)) + const hash = Hash.of(this.serialize(values, salt)); return { hash, salt, @@ -43,7 +43,7 @@ abstract class SealService { protected _verify(sealable: string[], seal: Seal): boolean { const { hash } = this._seal(sealable, seal.salt, seal.version) - return hash === seal.hash; + return hash.equalTo(seal.hash); } private serialize(values: string [], salt: string): string { diff --git a/src/logion/services/tokensrecord.service.ts b/src/logion/services/tokensrecord.service.ts index 96254d7c..9febf968 100644 --- a/src/logion/services/tokensrecord.service.ts +++ b/src/logion/services/tokensrecord.service.ts @@ -1,15 +1,16 @@ import { injectable } from "inversify"; import { DefaultTransactional, PolkadotService, requireDefined } from "@logion/rest-api-core"; -import { UUID, TypesTokensRecord, Adapters, TypesTokensRecordFile } from "@logion/node-api"; +import { UUID, TypesTokensRecord, Adapters, TypesTokensRecordFile, Hash } from "@logion/node-api"; import { TokensRecordAggregateRoot, TokensRecordRepository } from "../model/tokensrecord.model.js"; +import { HexString } from "@polkadot/util/types"; export interface GetTokensRecordParams { collectionLocId: string, - recordId: string, + recordId: Hash, } export interface GetTokensRecordFileParams extends GetTokensRecordParams { - hash: string + hash: Hash; } @injectable() @@ -24,7 +25,7 @@ export class LogionNodeTokensRecordService { const api = await this.polkadotService.readyApi(); const substrateObject = await api.polkadot.query.logionLoc.tokensRecordsMap( api.adapters.toNonCompactLocId(new UUID(collectionLocId)), - recordId + api.adapters.toH256(recordId), ); if(substrateObject.isSome) { return Adapters.toTokensRecord(substrateObject.unwrap()); @@ -36,7 +37,7 @@ export class LogionNodeTokensRecordService { async getTokensRecordFile(params: GetTokensRecordFileParams): Promise { const { hash } = params; const record = await this.getTokensRecord(params); - return record?.files.find(itemFile => itemFile.hash === hash); + return record?.files.find(itemFile => itemFile.hash.equalTo(hash)); } } @@ -49,7 +50,7 @@ export abstract class TokensRecordService { async addTokensRecord(item: TokensRecordAggregateRoot): Promise { const previousItem = await this.tokensRecordRepository.findBy( requireDefined(item.collectionLocId), - requireDefined(item.recordId), + requireDefined(Hash.fromHex(item.recordId as HexString)), ); if(previousItem) { throw new Error("Cannot replace existing item"); @@ -61,7 +62,7 @@ export abstract class TokensRecordService { await this.tokensRecordRepository.delete(item); } - async update(collectionLocId: string, recordId: string, mutator: (item: TokensRecordAggregateRoot) => Promise): Promise { + async update(collectionLocId: string, recordId: Hash, mutator: (item: TokensRecordAggregateRoot) => Promise): Promise { const item = requireDefined(await this.tokensRecordRepository.findBy(collectionLocId, recordId)); await mutator(item); await this.tokensRecordRepository.save(item); @@ -89,7 +90,7 @@ export class TransactionalTokensRecordService extends TokensRecordService { } @DefaultTransactional() - async update(collectionLocId: string, recordId: string, mutator: (item: TokensRecordAggregateRoot) => Promise): Promise { + async update(collectionLocId: string, recordId: Hash, mutator: (item: TokensRecordAggregateRoot) => Promise): Promise { return super.update(collectionLocId, recordId, mutator); } } diff --git a/test/helpers/Mock.ts b/test/helpers/Mock.ts new file mode 100644 index 00000000..0f4acbd4 --- /dev/null +++ b/test/helpers/Mock.ts @@ -0,0 +1,6 @@ +import { Hash } from "@logion/node-api"; +import { It } from "moq.ts"; + +export function ItIsHash(expected: Hash) { + return It.Is(given => given.equalTo(expected)); +} diff --git a/test/integration/migration/migration.spec.ts b/test/integration/migration/migration.spec.ts index d27345dc..62ddcbe5 100644 --- a/test/integration/migration/migration.spec.ts +++ b/test/integration/migration/migration.spec.ts @@ -1,6 +1,5 @@ -import { TestDb } from "@logion/rest-api-core"; +import { Log, TestDb } from "@logion/rest-api-core"; import { MigrationInterface, QueryRunner } from "typeorm"; -import { Log } from "@logion/rest-api-core"; const { logger } = Log; const { connect, disconnect, queryRunner, allMigrations } = TestDb; @@ -8,6 +7,7 @@ const { connect, disconnect, queryRunner, allMigrations } = TestDb; describe('Migration', () => { const NUM_OF_TABLES = 22; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; beforeEach(async () => { await connect([ "src/logion/model/*.model.ts" ], [ "src/logion/migration/*.ts" ], false); @@ -17,12 +17,12 @@ describe('Migration', () => { await disconnect(); }); - async function testMigrationUp(migration: MigrationInterface, runner:QueryRunner) { + async function testMigrationUp(migration: MigrationInterface, runner: QueryRunner) { logger.info("Migrating UP %s ", migration.name) await migration.up(runner) } - async function runAllMigrations(runner:QueryRunner) { + async function runAllMigrations(runner: QueryRunner) { for (const migration of allMigrations()) { await testMigrationUp(migration, runner); } @@ -31,18 +31,18 @@ describe('Migration', () => { it("executes all up()", async () => { // Given - const runner = queryRunner() - const tablesBefore = await runner.getTables() + const runner = queryRunner(); + const tablesBefore = await runner.getTables(); // When await runAllMigrations(runner) // Then const tablesAfter = await runner.getTables(); - expect(tablesAfter.length - tablesBefore.length).toBe(NUM_OF_TABLES) + expect(tablesAfter.length - tablesBefore.length).toBe(NUM_OF_TABLES); }) - async function testMigrationDown(migration: MigrationInterface, runner:QueryRunner) { + async function testMigrationDown(migration: MigrationInterface, runner: QueryRunner) { logger.info("Migrating DOWN %s ", migration.name) await migration.down(runner) } diff --git a/test/integration/model/collection.model.spec.ts b/test/integration/model/collection.model.spec.ts index f07deee6..7a8fe624 100644 --- a/test/integration/model/collection.model.spec.ts +++ b/test/integration/model/collection.model.spec.ts @@ -8,6 +8,7 @@ import { } from "../../../src/logion/model/collection.model.js"; import moment from "moment"; import { CollectionService, TransactionalCollectionService } from "../../../src/logion/services/collection.service.js"; +import { Hash } from "@logion/node-api"; const { connect, disconnect, checkNumOfRows, executeScript } = TestDb; @@ -59,34 +60,34 @@ describe("CollectionRepository", () => { it("finds a Collection Item with no files", async () => { const collectionLocId = "2035224b-ef77-4a69-aac4-e74bd030675d"; - const itemId = "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"; + const itemId = Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); const collectionItem = await repository.findBy( collectionLocId, itemId ); expect(collectionItem).toBeDefined() expect(collectionItem?.collectionLocId).toEqual(collectionLocId) - expect(collectionItem?.itemId).toEqual(itemId) + expect(collectionItem?.itemId).toEqual(itemId.toHex()) expect(collectionItem?.addedOn?.toISOString()).toEqual("2022-02-16T17:28:42.000Z") expect(collectionItem?.files).toEqual([]) }) it("finds a Collection Item with 2 files, one being delivered and some T&Cs", async () => { const collectionLocId = "296d3d8f-057f-445c-b4c8-59aa7d2d21de"; - const itemId = "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"; + const itemId = Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); const collectionItem = await repository.findBy( collectionLocId, itemId ); expect(collectionItem).toBeDefined() expect(collectionItem?.collectionLocId).toEqual(collectionLocId) - expect(collectionItem?.itemId).toEqual(itemId) + expect(collectionItem?.itemId).toEqual(itemId.toHex()) expect(collectionItem?.addedOn?.toISOString()).toEqual("2022-02-16T17:28:42.000Z") expect(collectionItem?.files?.length).toEqual(2) expect(collectionItem?.files?.map(file => file.cid)).toContain("123456") expect(collectionItem?.files?.map(file => file.cid)).toContain("78910") - const deliveredList = collectionItem?.getFile("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83").delivered!; + const deliveredList = collectionItem?.getFile(Hash.fromHex("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83")).delivered!; expect(deliveredList.length).toBe(2) const delivered1 = deliveredList.find(delivered => delivered.deliveredFileHash === "0x38c79034a97d8827559f883790d52a1527f6e7d37e66ac8e70bafda216fda6d7"); @@ -108,8 +109,8 @@ describe("CollectionRepository", () => { it("sets a file's CID", async () => { // Given const collectionLocId = "c38e5ab8-785f-4e26-91bd-f9cdef82f601"; - const itemId = "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"; - const hash = "0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83"; + const itemId = Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); + const hash = Hash.fromHex("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83"); const cid = "147852"; await service.update(collectionLocId, itemId, async item => { @@ -119,8 +120,8 @@ describe("CollectionRepository", () => { await checkNumOfRows(`SELECT * FROM collection_item_file WHERE collection_loc_id = '${ collectionLocId }' - AND item_id = '${ itemId }' - AND hash = '${ hash }' + AND item_id = '${ itemId.toHex() }' + AND hash = '${ hash.toHex() }' AND cid = '${ cid }'`, 1) }) @@ -132,15 +133,15 @@ describe("CollectionRepository", () => { await repository.save(collectionItem); - const updated = await repository.findBy(collectionItem.collectionLocId, collectionItem.itemId); + const updated = await repository.findBy(collectionItem.collectionLocId, Hash.fromHex(collectionItem.itemId)); expect(updated?.files?.length).toEqual(2) }) it("Adds delivery to file", async () => { const collectionLocId = '52d29fe9-983f-44d2-9e23-c8cb542981a3'; - const itemId = "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"; - const hash = "0x8bd8548beac4ce719151dc2ae893f8edc658a566e5ff654104783e14fb44012e"; - const deliveredFileHash = "0x38c79034a97d8827559f883790d52a1527f6e7d37e66ac8e70bafda216fda6d7"; + const itemId = Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); + const hash = Hash.fromHex("0x8bd8548beac4ce719151dc2ae893f8edc658a566e5ff654104783e14fb44012e"); + const deliveredFileHash = Hash.fromHex("0x38c79034a97d8827559f883790d52a1527f6e7d37e66ac8e70bafda216fda6d7"); const generatedOn = moment(); const owner = "0x900edc98db53508e6742723988B872dd08cd09c2"; await service.update(collectionLocId, itemId, async item => { @@ -156,8 +157,8 @@ describe("CollectionRepository", () => { it("finds latest delivery", async () => { const delivered = await repository.findLatestDelivery({ collectionLocId: "296d3d8f-057f-445c-b4c8-59aa7d2d21de", - itemId: "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee", - fileHash: "0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83", + itemId: Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"), + fileHash: Hash.fromHex("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83"), }); expect(delivered?.deliveredFileHash).toBe("0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2"); }) @@ -165,7 +166,7 @@ describe("CollectionRepository", () => { it("finds latest deliveries", async () => { const delivered = await repository.findLatestDeliveries({ collectionLocId: "296d3d8f-057f-445c-b4c8-59aa7d2d21de", - itemId: "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee", + itemId: Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"), }); expect("0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83" in delivered).toBe(true); const fileDeliveries = delivered["0x979ff1da4670561bf3f521a1a1d4aad097d617d2fa2c0e75d52efe90e7b7ce83"]; diff --git a/test/integration/model/locrequest.model.spec.ts b/test/integration/model/locrequest.model.spec.ts index c98e5603..f81195d6 100644 --- a/test/integration/model/locrequest.model.spec.ts +++ b/test/integration/model/locrequest.model.spec.ts @@ -14,18 +14,17 @@ import { ALICE, BOB } from "../../helpers/addresses.js"; import { v4 as uuid } from "uuid"; import { LocRequestService, TransactionalLocRequestService } from "../../../src/logion/services/locrequest.service.js"; import { polkadotAccount } from "../../../src/logion/model/supportedaccountid.model.js"; -import { UUID } from "@logion/node-api"; +import { Hash, UUID } from "@logion/node-api"; const SUBMITTER = polkadotAccount("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); const { connect, disconnect, checkNumOfRows, executeScript } = TestDb; const ENTITIES = [ LocRequestAggregateRoot, LocFile, LocMetadataItem, LocLink, LocFileDelivered ]; +const hash = Hash.fromHex("0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); +const anotherHash = Hash.fromHex("0x5a60f0a435fa1c508ccc7a7dd0a0fe8f924ba911b815b10c9ef0ddea0c49052e"); +const collectionLocId = "15ed922d-5960-4147-a73f-97d362cb7c46"; describe('LocRequestRepository - read accesses', () => { - const hash = "0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"; - const anotherHash = "0x5a60f0a435fa1c508ccc7a7dd0a0fe8f924ba911b815b10c9ef0ddea0c49052e"; - const collectionLocId = "15ed922d-5960-4147-a73f-97d362cb7c46"; - beforeAll(async () => { await connect(ENTITIES); await executeScript("test/integration/model/loc_requests.sql"); @@ -114,7 +113,7 @@ describe('LocRequestRepository - read accesses', () => { expect(request!.hasFile(hash)).toBe(true); const file = request!.getFile(hash); expect(file.name).toBe("a file"); - expect(file.hash).toBe(hash); + expect(file.hash).toEqual(hash); expect(file.oid).toBe(123456); expect(file.addedOn!.isSame(moment("2021-10-06T11:16:00.000"))).toBe(true); expect(file.nature).toBe("some nature"); @@ -124,7 +123,7 @@ describe('LocRequestRepository - read accesses', () => { const metadata = request!.getMetadataItems(request?.getOwner()); expect(metadata.length).toBe(1); expect(metadata[0].name).toBe("a name"); - expect(metadata[0].nameHash).toBe("0x36e96c62633613d6f8e98943830ed5c5f814c2bb9214d8bba577386096bc926a"); + expect(metadata[0].nameHash.toHex()).toBe("0x36e96c62633613d6f8e98943830ed5c5f814c2bb9214d8bba577386096bc926a"); expect(metadata[0].value).toBe("a value"); expect(metadata[0].addedOn!.isSame(moment("2021-10-06T11:16:00.000"))).toBe(true); expect(request!.metadata![0].status).toBe("DRAFT"); @@ -203,7 +202,7 @@ describe('LocRequestRepository - read accesses', () => { hash, }) checkDelivery(delivered); - expect(delivered[anotherHash]).toBeUndefined(); + expect(delivered[anotherHash.toHex()]).toBeUndefined(); }) it("finds all deliveries", async () => { @@ -211,21 +210,21 @@ describe('LocRequestRepository - read accesses', () => { collectionLocId: collectionLocId, }) checkDelivery(delivered); - expect(delivered[anotherHash].length).toEqual(1); - expect(delivered[anotherHash][0].owner).toEqual("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); - expect(delivered[anotherHash][0].deliveredFileHash).toEqual("0xdbfaa07666457afd3cdc6fb2726a94cde7a0f613a0f354e695b315372a098e8a"); + expect(delivered[anotherHash.toHex()].length).toEqual(1); + expect(delivered[anotherHash.toHex()][0].owner).toEqual("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); + expect(delivered[anotherHash.toHex()][0].deliveredFileHash).toEqual("0xdbfaa07666457afd3cdc6fb2726a94cde7a0f613a0f354e695b315372a098e8a"); }) it("finds one delivery by copy hash", async () => { - const deliveredFileHash = "0xdbfaa07666457afd3cdc6fb2726a94cde7a0f613a0f354e695b315372a098e8a"; + const deliveredFileHash = Hash.fromHex("0xdbfaa07666457afd3cdc6fb2726a94cde7a0f613a0f354e695b315372a098e8a"); const delivered = await repository.findDeliveryByDeliveredFileHash({ collectionLocId, deliveredFileHash}) expect(delivered?.owner).toEqual("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); expect(delivered?.hash).toEqual("0x5a60f0a435fa1c508ccc7a7dd0a0fe8f924ba911b815b10c9ef0ddea0c49052e"); - expect(delivered?.deliveredFileHash).toEqual(deliveredFileHash); + expect(delivered?.deliveredFileHash).toEqual(deliveredFileHash.toHex()); }) it("finds no delivery with unknown copy hash", async () => { - const delivered = await repository.findDeliveryByDeliveredFileHash({ collectionLocId, deliveredFileHash: "0xb8af3be22a2395a9961cfe43cdbc7e731f334c8272a9903db29d4ff584b3934a"}) + const delivered = await repository.findDeliveryByDeliveredFileHash({ collectionLocId, deliveredFileHash: Hash.fromHex("0xb8af3be22a2395a9961cfe43cdbc7e731f334c8272a9903db29d4ff584b3934a")}) expect(delivered).toBeNull(); }) @@ -239,13 +238,13 @@ describe('LocRequestRepository - read accesses', () => { }) function checkDelivery(delivered: Record) { - expect(delivered[hash].length).toEqual(3); - expect(delivered[hash][0].owner).toEqual("5Eewz58eEPS81847EezkiFENE3kG8fxrx1BdRWyFJAudPC6m"); - expect(delivered[hash][0].deliveredFileHash).toEqual("0xc14d46b478dcb21833b90dc9880aa3a7507b01aa5d033c64051df999f3c3bba0"); - expect(delivered[hash][1].owner).toEqual("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); - expect(delivered[hash][1].deliveredFileHash).toEqual("0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2"); - expect(delivered[hash][2].owner).toEqual("5H9ZP7zyJtmay2Vcstf7SzK8LD1PGe5PJ8q7xakqp4zXFEwz"); - expect(delivered[hash][2].deliveredFileHash).toEqual("0x38df2378ed26e20124d8c38a945af1b4a058656aab3b3b1f71a9d8a629cc0d81"); + expect(delivered[hash.toHex()].length).toEqual(3); + expect(delivered[hash.toHex()][0].owner).toEqual("5Eewz58eEPS81847EezkiFENE3kG8fxrx1BdRWyFJAudPC6m"); + expect(delivered[hash.toHex()][0].deliveredFileHash).toEqual("0xc14d46b478dcb21833b90dc9880aa3a7507b01aa5d033c64051df999f3c3bba0"); + expect(delivered[hash.toHex()][1].owner).toEqual("5DDGQertEH5qvKVXUmpT3KNGViCX582Qa2WWb8nGbkmkRHvw"); + expect(delivered[hash.toHex()][1].deliveredFileHash).toEqual("0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2"); + expect(delivered[hash.toHex()][2].owner).toEqual("5H9ZP7zyJtmay2Vcstf7SzK8LD1PGe5PJ8q7xakqp4zXFEwz"); + expect(delivered[hash.toHex()][2].deliveredFileHash).toEqual("0x38df2378ed26e20124d8c38a945af1b4a058656aab3b3b1f71a9d8a629cc0d81"); } }) @@ -316,7 +315,7 @@ describe('LocRequestRepository.save()', () => { await service.addNewRequest(locRequest); await service.update(id, async request => { request.setFileRestrictedDelivery({ - hash: "hash", + hash, restrictedDelivery: true, }); }); @@ -381,7 +380,7 @@ function givenLoc(id: string, locType: LocType, status: "OPEN" | "DRAFT"): LocRe locRequest.files = [] locRequest.addFile({ name: "fileName", - hash: "hash", + hash, oid: 123, contentType: "content/type", nature: "nature1", diff --git a/test/unit/controllers/collection.controller.spec.ts b/test/unit/controllers/collection.controller.spec.ts index 8fbca072..10ffa7d9 100644 --- a/test/unit/controllers/collection.controller.spec.ts +++ b/test/unit/controllers/collection.controller.spec.ts @@ -1,7 +1,7 @@ import { TestApp } from "@logion/rest-api-core"; import { Container } from "inversify"; import { Mock, It } from "moq.ts"; -import { CollectionItem, ItemFile, hashString } from "@logion/node-api"; +import { CollectionItem, Hash, ItemFile } from "@logion/node-api"; import { writeFile } from "fs/promises"; import { CollectionController } from "../../../src/logion/controllers/collection.controller.js"; import { @@ -37,21 +37,22 @@ import { NonTransactionalLocRequestService } from "../../../src/logion/services/locrequest.service.js"; import { polkadotAccount, EmbeddableSupportedAccountId } from "../../../src/logion/model/supportedaccountid.model.js"; +import { ItIsHash } from "../../helpers/Mock.js"; const collectionLocId = "d61e2e12-6c06-4425-aeee-2a0e969ac14e"; const collectionLocOwner = ALICE; const collectionRequester = "5EBxoSssqNo23FvsDeUxjyQScnfEiGxJaNwuwqBH2Twe35BX"; -const itemId = "0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"; +const itemId = Hash.fromHex("0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"); const timestamp = moment(); const SOME_DATA = 'some data'; -const SOME_DATA_HASH = '0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'; +const SOME_DATA_HASH = Hash.fromHex('0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'); const FILE_NAME = "'a-file.pdf'"; const CID = "cid-784512"; const CONTENT_TYPE = "text/plain"; const ITEM_TOKEN_OWNER = "0x900edc98db53508e6742723988B872dd08cd09c3"; -const DELIVERY_HASH = '0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2'; +const DELIVERY_HASH = Hash.fromHex('0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2'); const { setupApp, mockAuthenticationWithAuthenticatedUser, mockAuthenticatedUser } = TestApp; @@ -75,14 +76,14 @@ describe("CollectionController", () => { const app = setupApp(CollectionController, container => mockModelForGet(container, true)); await request(app) - .get(`/api/collection/${ collectionLocId }/items/${ itemId }`) + .get(`/api/collection/${ collectionLocId }/items/${ itemId.toHex() }`) .send() .expect(200) .then(response => { expect(response.body.collectionLocId).toEqual(collectionLocId) - expect(response.body.itemId).toEqual(itemId) + expect(response.body.itemId).toEqual(itemId.toHex()) expect(response.body.addedOn).toEqual(timestamp.toISOString()) - expect(response.body.files[0].hash).toEqual(SOME_DATA_HASH) + expect(response.body.files[0].hash).toEqual(SOME_DATA_HASH.toHex()) }) }) @@ -90,12 +91,13 @@ describe("CollectionController", () => { const app = setupApp(CollectionController, container => mockModelForGet(container, false)); + const unknownItem = Hash.of("unknown item").toHex(); await request(app) - .get(`/api/collection/${ collectionLocId }/items/0x12345`) + .get(`/api/collection/${ collectionLocId }/items/${ unknownItem }`) .send() .expect(400) .then(response => { - expect(response.body.errorMessage).toEqual("Collection item d61e2e12-6c06-4425-aeee-2a0e969ac14e/0x12345 not found") + expect(response.body.errorMessage).toEqual(`Collection item d61e2e12-6c06-4425-aeee-2a0e969ac14e/${ unknownItem } not found`) }) }) @@ -109,8 +111,8 @@ describe("CollectionController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(200); }) @@ -124,14 +126,15 @@ describe("CollectionController", () => { fileType: "Item", })); const buffer = Buffer.from(SOME_DATA); + const wrongHash = Hash.of("wrong-hash").toHex(); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: "wrong-hash" }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: wrongHash }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) .then(response => { - expect(response.body.errorMessage).toBe("Received hash wrong-hash does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); + expect(response.body.errorMessage).toBe(`Received hash ${ wrongHash } does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee`); }); }) @@ -145,8 +148,8 @@ describe("CollectionController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -165,8 +168,8 @@ describe("CollectionController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -185,8 +188,8 @@ describe("CollectionController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -205,8 +208,8 @@ describe("CollectionController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/collection/${ collectionLocId }/${ itemId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: "WrongName.pdf" }) .expect(400) .expect('Content-Type', /application\/json/) @@ -229,7 +232,7 @@ describe("CollectionController", () => { isOwner: true, })); await request(app) - .get(`/api/collection/${ collectionLocId }/items/${ itemId }/check`) + .get(`/api/collection/${ collectionLocId }/items/${ itemId.toHex() }/check`) .expect(200); }) @@ -241,7 +244,7 @@ describe("CollectionController", () => { isOwner: false })); await request(app) - .get(`/api/collection/${ collectionLocId }/items/${ itemId }/check`) + .get(`/api/collection/${ collectionLocId }/items/${ itemId.toHex() }/check`) .expect(403); }) @@ -253,7 +256,7 @@ describe("CollectionController", () => { isOwner: false })); await request(app) - .get(`/api/collection/${ collectionLocId }/items/${ itemId }/check`) + .get(`/api/collection/${ collectionLocId }/items/${ itemId.toHex() }/check`) .expect(400); }) @@ -265,8 +268,8 @@ function testDownloadFiles(fileType: FileType) { const url = fileType === "Item" ? - `/api/collection/${ collectionLocId }/${ itemId }/files/${ SOME_DATA_HASH }` : - `/api/collection/${ collectionLocId }/files/${ SOME_DATA_HASH }/${ itemId }`; + `/api/collection/${ collectionLocId }/${ itemId.toHex() }/files/${ SOME_DATA_HASH.toHex() }` : + `/api/collection/${ collectionLocId }/files/${ SOME_DATA_HASH.toHex() }/${ itemId.toHex() }`; it('fails to download existing file given its hash if no restricted delivery', async () => { const app = setupApp(CollectionController, container => mockModel(container, { @@ -276,7 +279,7 @@ function testDownloadFiles(fileType: FileType) { restrictedDelivery: false, fileType, })); - const filePath = CollectionController.tempFilePath({ collectionLocId, itemId, hash: SOME_DATA_HASH}); + const filePath = CollectionController.tempFilePath({ collectionLocId, itemId, hash: SOME_DATA_HASH }); await writeFile(filePath, SOME_DATA); await request(app) .get(url) @@ -393,10 +396,10 @@ describe("CollectionController - item files - ", () => { fileType: "Item", })); await request(app) - .get(`/api/collection/${ collectionLocId }/${ itemId }/latest-deliveries`) + .get(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/latest-deliveries`) .expect(200) .expect('Content-Type', /application\/json/) - .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH][0])); + .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH.toHex()][0])); }) it('retrieves deliveries info', async () => { @@ -409,10 +412,10 @@ describe("CollectionController - item files - ", () => { fileType: "Item", })); await request(app) - .get(`/api/collection/${ collectionLocId }/${ itemId }/all-deliveries`) + .get(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/all-deliveries`) .expect(200) .expect('Content-Type', /application\/json/) - .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH][0])); + .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH.toHex()][0])); }) it('downloads existing file source given its hash and is owner', async () => { @@ -428,7 +431,7 @@ describe("CollectionController - item files - ", () => { const filePath = CollectionController.tempFilePath({ collectionLocId, itemId, hash: SOME_DATA_HASH}); await writeFile(filePath, SOME_DATA); await request(app) - .get(`/api/collection/${ collectionLocId }/${ itemId }/files/${ SOME_DATA_HASH }/source`) + .get(`/api/collection/${ collectionLocId }/${ itemId.toHex() }/files/${ SOME_DATA_HASH.toHex() }/source`) .expect(200, SOME_DATA) .expect('Content-Type', /text\/plain/); let fileReallyExists = true; @@ -458,7 +461,7 @@ describe("CollectionController - collection files - ", () => { it('updates restricted delivery flag', async () => { const app = setupApp(CollectionController, container => mockModel(container, mockParams)); await request(app) - .put(`/api/collection/${ collectionLocId }/files/${ SOME_DATA_HASH }`) + .put(`/api/collection/${ collectionLocId }/files/${ SOME_DATA_HASH.toHex() }`) .send({ restrictedDelivery: true }) .expect(200) }) @@ -466,7 +469,7 @@ describe("CollectionController - collection files - ", () => { it('retrieves deliveries info', async () => { const app = setupApp(CollectionController, container => mockModel(container, mockParams)); await request(app) - .get(`/api/collection/${ collectionLocId }/file-deliveries/${ SOME_DATA_HASH }`) + .get(`/api/collection/${ collectionLocId }/file-deliveries/${ SOME_DATA_HASH.toHex() }`) .expect(200) .expect('Content-Type', /application\/json/) .then(response => { @@ -482,7 +485,7 @@ describe("CollectionController - collection files - ", () => { .expect(200) .expect('Content-Type', /application\/json/) .then(response => { - const delivery = response.body[SOME_DATA_HASH][0]; + const delivery = response.body[SOME_DATA_HASH.toHex()][0]; checkDelivery(delivery); }); }) @@ -492,7 +495,7 @@ describe("CollectionController - collection files - ", () => { await request(app) .put(`/api/collection/${ collectionLocId }/file-deliveries`) .send({ - copyHash: DELIVERY_HASH + copyHash: DELIVERY_HASH.toHex() }) .expect(200) .expect('Content-Type', /application\/json/) @@ -507,7 +510,7 @@ describe("CollectionController - collection files - ", () => { await request(app) .put(`/api/collection/${ collectionLocId }/file-deliveries`) .send({ - copyHash: "0x11223344" + copyHash: Hash.of("wrong hash").toHex() }) .expect(400) .expect('Content-Type', /application\/json/) @@ -516,11 +519,11 @@ describe("CollectionController - collection files - ", () => { }); }) - function checkDelivery(delivery: any, originalFileHash?: string) { - expect(delivery.copyHash).toBe(DELIVERY_HASH); + function checkDelivery(delivery: any, originalFileHash?: Hash) { + expect(delivery.copyHash).toBe(DELIVERY_HASH.toHex()); expect(delivery.generatedOn).toBeDefined(); expect(delivery.owner).toBe(ITEM_TOKEN_OWNER); - expect(delivery.originalFileHash).toBe(originalFileHash); + expect(delivery.originalFileHash).toBe(originalFileHash?.toHex()); } }) @@ -549,7 +552,7 @@ function mockModel( const collectionLoc = new LocRequestAggregateRoot(); collectionLoc.id = collectionLocId; collectionItem.collectionLocId = collectionLocId; - collectionItem.itemId = itemId; + collectionItem.itemId = itemId.toHex(); collectionItem.addedOn = timestamp.toDate(); collectionItem.token = new CollectionItemToken(); collectionItem.token.id = TOKEN_ID; @@ -557,8 +560,8 @@ function mockModel( if (fileType === "Item") { const collectionItemFile = new CollectionItemFile() collectionItemFile.collectionLocId = collectionLocId; - collectionItemFile.itemId = itemId; - collectionItemFile.hash = SOME_DATA_HASH; + collectionItemFile.itemId = itemId.toHex(); + collectionItemFile.hash = SOME_DATA_HASH.toHex(); collectionItemFile.name = FILE_NAME; collectionItemFile.contentType = CONTENT_TYPE; if(hasFile && fileAlreadyUploaded) { @@ -571,7 +574,7 @@ function mockModel( } else if (hasFile && fileType === "Collection") { const collectionFile = new LocFile(); collectionFile.lifecycle = EmbeddableLifecycle.from(true); - collectionFile.hash = SOME_DATA_HASH; + collectionFile.hash = SOME_DATA_HASH.toHex(); if(fileAlreadyUploaded) { collectionFile.cid = CID; } @@ -593,12 +596,12 @@ function mockModel( const collectionRepository = new Mock() const locRequestRepository = new Mock(); if (collectionItemPublished) { - collectionRepository.setup(instance => instance.findBy(collectionLocId, itemId)) + collectionRepository.setup(instance => instance.findBy(collectionLocId, ItIsHash(itemId))) .returns(Promise.resolve(collectionItem)) collectionRepository.setup(instance => instance.findAllBy(collectionLocId)) .returns(Promise.resolve([ collectionItem ])) } else { - collectionRepository.setup(instance => instance.findBy(collectionLocId, itemId)) + collectionRepository.setup(instance => instance.findBy(collectionLocId, ItIsHash(itemId))) .returns(Promise.resolve(null)) collectionRepository.setup(instance => instance.findAllBy(collectionLocId)) .returns(Promise.resolve([])) @@ -607,44 +610,44 @@ function mockModel( if(restrictedDelivery && fileType === "Item") { const delivered: CollectionItemFileDelivered = { collectionLocId, - itemId, - hash: SOME_DATA_HASH, - deliveredFileHash: DELIVERY_HASH, + itemId: itemId.toHex(), + hash: SOME_DATA_HASH.toHex(), + deliveredFileHash: DELIVERY_HASH.toHex(), generatedOn: new Date(), owner: ITEM_TOKEN_OWNER, collectionItemFile: collectionItemFile.object(), }; collectionRepository.setup(instance => instance.findLatestDelivery( - It.Is<{ collectionLocId: string, itemId: string, fileHash: string }>(query => + It.Is<{ collectionLocId: string, itemId: Hash, fileHash: Hash }>(query => query.collectionLocId === collectionLocId - && query.itemId === itemId - && query.fileHash === SOME_DATA_HASH + && query.itemId.equalTo(itemId) + && query.fileHash.equalTo(SOME_DATA_HASH) )) ).returnsAsync(delivered); collectionRepository.setup(instance => instance.findLatestDeliveries( - It.Is<{ collectionLocId: string, itemId: string }>(query => + It.Is<{ collectionLocId: string, itemId: Hash }>(query => query.collectionLocId === collectionLocId - && query.itemId === itemId + && query.itemId.equalTo(itemId) )) ).returnsAsync({ - [ SOME_DATA_HASH ]: [ delivered ] + [ SOME_DATA_HASH.toHex() ]: [ delivered ] }); } else if (restrictedDelivery && fileType === "Collection") { const fileDelivered: LocFileDelivered[] = [ { requestId: collectionLocId, - hash: SOME_DATA_HASH, - deliveredFileHash: DELIVERY_HASH, + hash: SOME_DATA_HASH.toHex(), + deliveredFileHash: DELIVERY_HASH.toHex(), generatedOn: new Date(), owner: ITEM_TOKEN_OWNER, } ] const delivered: Record = {}; - delivered[SOME_DATA_HASH] = fileDelivered; + delivered[SOME_DATA_HASH.toHex()] = fileDelivered; locRequestRepository.setup(instance => instance.findAllDeliveries( - It.Is<{ collectionLocId: string, hash: string }>(query => + It.Is<{ collectionLocId: string, hash: Hash }>(query => query.collectionLocId === collectionLocId && - query.hash === SOME_DATA_HASH )) + query.hash.equalTo(SOME_DATA_HASH) )) ).returnsAsync(delivered); locRequestRepository.setup(instance => instance.findAllDeliveries( It.Is<{ collectionLocId: string, hash: string }>(query => @@ -652,15 +655,15 @@ function mockModel( query.hash === undefined )) ).returnsAsync(delivered); locRequestRepository.setup(instance => instance.findDeliveryByDeliveredFileHash( - It.Is<{ collectionLocId: string, deliveredFileHash: string }>(query => + It.Is<{ collectionLocId: string, deliveredFileHash: Hash }>(query => query.collectionLocId === collectionLocId && - query.deliveredFileHash === DELIVERY_HASH + query.deliveredFileHash.equalTo(DELIVERY_HASH) )) ).returnsAsync(fileDelivered[0]); locRequestRepository.setup(instance => instance.findDeliveryByDeliveredFileHash( - It.Is<{ collectionLocId: string, deliveredFileHash: string }>(query => + It.Is<{ collectionLocId: string, deliveredFileHash: Hash }>(query => query.collectionLocId !== collectionLocId || - query.deliveredFileHash !== DELIVERY_HASH + !query.deliveredFileHash.equalTo(DELIVERY_HASH) )) ).returnsAsync(null); } @@ -689,30 +692,30 @@ function mockModel( const publishedCollectionItemFile: ItemFile = { hash: SOME_DATA_HASH, - name: hashString(FILE_NAME), - contentType: hashString(CONTENT_TYPE), + name: Hash.of(FILE_NAME), + contentType: Hash.of(CONTENT_TYPE), size: BigInt(SOME_DATA.length) } const publishedCollectionItem: CollectionItem = { id: itemId, - description: hashString("Item Description"), + description: Hash.of("Item Description"), files: [ publishedCollectionItemFile ], restrictedDelivery, termsAndConditions: [], token: { - id: hashString(TOKEN_ID), - type: hashString(TOKEN_TYPE), + id: Hash.of(TOKEN_ID), + type: Hash.of(TOKEN_TYPE), issuance: 1n, } } const logionNodeCollectionService = new Mock(); logionNodeCollectionService.setup(instance => instance.getCollectionItem(It.Is( - param => param.collectionLocId === collectionLocId && param.itemId === itemId) + param => param.collectionLocId === collectionLocId && param.itemId.equalTo(itemId)) )) .returns(Promise.resolve(collectionItemPublished ? publishedCollectionItem : undefined)); logionNodeCollectionService.setup(instance => instance.getCollectionItemFile(It.Is( - param => param.collectionLocId === collectionLocId && param.itemId === itemId && param.hash === SOME_DATA_HASH) + param => param.collectionLocId === collectionLocId && param.itemId.equalTo(itemId) && param.hash.equalTo(SOME_DATA_HASH)) )) .returns(Promise.resolve(hasFile ? publishedCollectionItemFile : undefined)); container.bind(LogionNodeCollectionService).toConstantValue(logionNodeCollectionService.object()); @@ -736,7 +739,7 @@ const TOKEN_ID = "some-token-id"; const TOKEN_TYPE = "owner"; function expectDeliveryInfo(responseBody: any) { - expect(responseBody.copyHash).toBe(DELIVERY_HASH); + expect(responseBody.copyHash).toBe(DELIVERY_HASH.toHex()); expect(responseBody.generatedOn).toBeDefined(); expect(responseBody.owner).toBe(ITEM_TOKEN_OWNER); expect(responseBody.belongsToCurrentOwner).toBe(true); diff --git a/test/unit/controllers/fileupload.spec.ts b/test/unit/controllers/fileupload.spec.ts index f8feb106..9876a36c 100644 --- a/test/unit/controllers/fileupload.spec.ts +++ b/test/unit/controllers/fileupload.spec.ts @@ -3,12 +3,13 @@ import { writeFile } from "fs/promises"; import os from "os"; import path from "path"; import { getUploadedFile } from "../../../src/logion/controllers/fileupload.js"; +import { Hash } from "@logion/node-api"; describe("getUploadedFile", () => { it("properly handles latin1-encoded file names", async () => { await withUploadedContent("data"); - const receivedHash = "0x3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7"; + const receivedHash = Hash.fromHex("0x3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7"); const fileName = "éééé.txt"; const request = mockRequest({ @@ -21,7 +22,7 @@ describe("getUploadedFile", () => { it("rejects when received hash does not match local hash", async () => { await withUploadedContent("data"); - const receivedHash = "0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2"; + const receivedHash = Hash.fromHex("0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2"); const request = mockRequest({ fileName: "some-file.txt", @@ -32,7 +33,7 @@ describe("getUploadedFile", () => { it("rejects when upload truncated", async () => { await withUploadedContent("dat"); - const receivedHash = "0x3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7"; + const receivedHash = Hash.fromHex("0x3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7"); const request = mockRequest({ fileName: "some-file.txt", diff --git a/test/unit/controllers/locrequest.controller.creation.spec.ts b/test/unit/controllers/locrequest.controller.creation.spec.ts index 9a02387c..57f8e7e7 100644 --- a/test/unit/controllers/locrequest.controller.creation.spec.ts +++ b/test/unit/controllers/locrequest.controller.creation.spec.ts @@ -166,7 +166,7 @@ async function testLocRequestCreationWithEmbeddedUserIdentity(isLegalOfficer: bo expect(response.body.status).toBe("REVIEW_PENDING"); expect(response.body.locType).toBe(locType); checkPrivateData(response, expectedUserPrivateData); - expect(response.body.seal).toEqual(SEAL.hash) + expect(response.body.seal).toEqual(SEAL.hash.toHex()) } else { expect(response.body.errorMessage).toEqual(expectedErrorMessage); } diff --git a/test/unit/controllers/locrequest.controller.fetch.spec.ts b/test/unit/controllers/locrequest.controller.fetch.spec.ts index ae9f2373..95ef4d1c 100644 --- a/test/unit/controllers/locrequest.controller.fetch.spec.ts +++ b/test/unit/controllers/locrequest.controller.fetch.spec.ts @@ -31,9 +31,8 @@ import { } from "./locrequest.controller.shared.js"; import { mockAuthenticationForUserOrLegalOfficer } from "@logion/rest-api-core/dist/TestApp.js"; import { UserPrivateData } from "src/logion/controllers/adapters/locrequestadapter.js"; -import { Fees } from "@logion/node-api"; +import { Fees, Hash } from "@logion/node-api"; import { polkadotAccount } from "../../../src/logion/model/supportedaccountid.model.js"; -import { sha256String } from "../../../src/logion/lib/crypto/hashing.js"; const { mockAuthenticationWithCondition, setupApp } = TestApp; @@ -218,7 +217,7 @@ const testFile: FileDescription = { name: "test-file", nature: "file-nature", contentType: "application/pdf", - hash: "0x9383cd5dfeb5870027088289c665c3bae2d339281840473f35311954e984dea9", + hash: Hash.fromHex("0x9383cd5dfeb5870027088289c665c3bae2d339281840473f35311954e984dea9"), oid: 123, submitter: SUBMITTER, addedOn: moment("2022-08-31T15:53:12.741Z"), @@ -238,7 +237,7 @@ const testLink: LinkDescription = { const testMetadataItem: MetadataItemDescription = { name: "test-data", - nameHash: sha256String("test-data"), + nameHash: Hash.of("test-data"), value: "test-data-value", submitter: SUBMITTER, fees: DATA_LINK_FEES, @@ -256,7 +255,7 @@ async function testGet(app: ReturnType, expectedUserPrivateData const file = response.body.files[0] expect(file.name).toBe(testFile.name) expect(file.nature).toBe(testFile.nature) - expect(file.hash).toBe(testFile.hash) + expect(file.hash).toBe(testFile.hash.toHex()) expect(file.addedOn).toBe(testFile.addedOn?.toISOString()) expect(file.submitter).toEqual(SUBMITTER) expect(file.fees.inclusion).toBe(FILE_FEES.inclusionFee.toString()) diff --git a/test/unit/controllers/locrequest.controller.items.spec.ts b/test/unit/controllers/locrequest.controller.items.spec.ts index 8ceedfd3..fb4dbeef 100644 --- a/test/unit/controllers/locrequest.controller.items.spec.ts +++ b/test/unit/controllers/locrequest.controller.items.spec.ts @@ -1,4 +1,5 @@ import { TestApp } from "@logion/rest-api-core"; +import { Hash } from "@logion/node-api"; import { Express } from 'express'; import { writeFile } from 'fs/promises'; import { LocRequestController } from "../../../src/logion/controllers/locrequest.controller.js"; @@ -31,7 +32,7 @@ import { SupportedAccountId, accountEquals } from "../../../src/logion/model/supportedaccountid.model.js"; -import { sha256String } from "../../../src/logion/lib/crypto/hashing.js"; +import { ItIsHash } from "../../helpers/Mock.js"; const { mockAuthenticationWithCondition, setupApp } = TestApp; @@ -58,11 +59,12 @@ describe('LocRequestController - Items -', () => { mockRequester(locRequest, REQUESTER); const app = setupApp(LocRequestController, container => mockModelForAddFile(container, locRequest, 'NOT_ISSUER')); const buffer = Buffer.from(SOME_DATA); + const wrongHash = Hash.of("wrong-hash").toHex(); await request(app) .post(`/api/loc-request/${ REQUEST_ID }/files`) .field({ nature: "some nature", - hash: "wrong-hash" + hash: wrongHash }) .attach('file', buffer, { filename: FILE_NAME, @@ -71,7 +73,7 @@ describe('LocRequestController - Items -', () => { .expect(400) .expect('Content-Type', /application\/json/) .then(response => { - expect(response.body.errorMessage).toBe("Received hash wrong-hash does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); + expect(response.body.errorMessage).toBe(`Received hash ${ wrongHash } does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee`); }); }) @@ -140,7 +142,7 @@ describe('LocRequestController - Items -', () => { it('confirms a file', async () => { const app = setupApp(LocRequestController, mockModelForConfirmFile) await request(app) - .put(`/api/loc-request/${REQUEST_ID}/files/${SOME_DATA_HASH}/confirm`) + .put(`/api/loc-request/${REQUEST_ID}/files/${ SOME_DATA_HASH.toHex() }/confirm`) .expect(204); }); @@ -189,7 +191,7 @@ describe('LocRequestController - Items -', () => { it('confirms a metadata item', async () => { const app = setupApp(LocRequestController, (container) => mockModelForConfirmMetadata(container)) await request(app) - .put(`/api/loc-request/${ REQUEST_ID }/metadata/${ SOME_DATA_NAME_HASH }/confirm`) + .put(`/api/loc-request/${ REQUEST_ID }/metadata/${ SOME_DATA_NAME_HASH.toHex() }/confirm`) .expect(204); }); @@ -227,14 +229,14 @@ describe('LocRequestController - Items -', () => { }); const SOME_DATA = 'some data'; -const SOME_DATA_HASH = '0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'; +const SOME_DATA_HASH = Hash.fromHex('0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'); const FILE_NAME = "'a-file.pdf'"; function mockModelForAddFile(container: Container, request: Mock, issuerMode: SetupIssuerMode): void { const { fileStorageService, loc } = buildMocksForUpdate(container, { request }); setupRequest(request, REQUEST_ID, "Transaction", "OPEN", testData); - request.setup(instance => instance.hasFile(SOME_DATA_HASH)).returns(false); + request.setup(instance => instance.hasFile(ItIsHash(SOME_DATA_HASH))).returns(false); request.setup(instance => instance.addFile(It.IsAny(), It.IsAny())).returns(); setupSelectedIssuer(loc, issuerMode); @@ -265,7 +267,7 @@ async function testAddFileForbidden(app: Express) { const buffer = Buffer.from(SOME_DATA); await request(app) .post(`/api/loc-request/${ REQUEST_ID }/files`) - .field({ nature: "some nature", hash: SOME_DATA_HASH }) + .field({ nature: "some nature", hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME, contentType: 'text/plain', @@ -281,12 +283,12 @@ function mockModelForDownloadFile(container: Container, issuerMode: SetupIssuerM mockRequester(request, REQUESTER); const hash = SOME_DATA_HASH; - request.setup(instance => instance.getFile(hash)).returns({ + request.setup(instance => instance.getFile(ItIsHash(hash))).returns({ ...SOME_FILE, submitter: issuerMode !== "NOT_ISSUER" ? ISSUER : REQUESTER, }); - const filePath = "/tmp/download-" + REQUEST_ID + "-" + hash; + const filePath = "/tmp/download-" + REQUEST_ID + "-" + hash.toHex(); fileStorageService.setup(instance => instance.exportFile({ oid: SOME_OID }, filePath)) .returns(Promise.resolve()); @@ -308,10 +310,10 @@ const SOME_FILE: FileDescription = { }; async function testDownloadSuccess(app: Express) { - const filePath = "/tmp/download-" + REQUEST_ID + "-" + SOME_DATA_HASH; + const filePath = "/tmp/download-" + REQUEST_ID + "-" + SOME_DATA_HASH.toHex(); await writeFile(filePath, SOME_DATA); await request(app) - .get(`/api/loc-request/${ REQUEST_ID }/files/${ SOME_DATA_HASH }`) + .get(`/api/loc-request/${ REQUEST_ID }/files/${ SOME_DATA_HASH.toHex() }`) .expect(200, SOME_DATA) .expect('Content-Type', /text\/plain/); await expectFileExists(filePath); @@ -332,7 +334,7 @@ async function expectFileExists(filePath: string) { async function testDownloadForbidden(app: Express) { await request(app) - .get(`/api/loc-request/${ REQUEST_ID }/files/${ SOME_DATA_HASH }`) + .get(`/api/loc-request/${ REQUEST_ID }/files/${ SOME_DATA_HASH.toHex() }`) .expect(403); } @@ -362,17 +364,17 @@ function mockModelForDeleteFile(container: Container, request: Mock, issuerMode: SetupIssuerMode) { await request(app) - .delete(`/api/loc-request/${REQUEST_ID}/files/${SOME_DATA_HASH}`) + .delete(`/api/loc-request/${ REQUEST_ID }/files/${ SOME_DATA_HASH.toHex() }`) .expect(200); if(issuerMode !== 'NOT_ISSUER') { locRequest.verify(instance => instance.removeFile( It.Is(account => accountEquals(account, ISSUER)), - SOME_DATA_HASH + ItIsHash(SOME_DATA_HASH) )); } else { locRequest.verify(instance => instance.removeFile( It.Is(account => accountEquals(account, ALICE_ACCOUNT)), - SOME_DATA_HASH + ItIsHash(SOME_DATA_HASH) )); } } @@ -380,22 +382,22 @@ async function testDeleteFileSuccess(app: Express, locRequest: Mock instance.getFile(SOME_DATA_HASH)).returns({ submitter: { type: "Polkadot", address: ALICE } } as FileDescription); - request.setup(instance => instance.confirmFile(SOME_DATA_HASH)).returns(); + request.setup(instance => instance.getFile(ItIsHash(SOME_DATA_HASH))).returns({ submitter: { type: "Polkadot", address: ALICE } } as FileDescription); + request.setup(instance => instance.confirmFile(ItIsHash(SOME_DATA_HASH))).returns(); mockPolkadotIdentityLoc(repository, false); } const SOME_DATA_NAME = "data name with exotic char !é\"/&'" -const SOME_DATA_NAME_HASH = sha256String("data name with exotic char !é\"/&'"); +const SOME_DATA_NAME_HASH = Hash.of("data name with exotic char !é\"/&'"); const SOME_DATA_VALUE = "data value with exotic char !é\"/&'" function mockRequestForMetadata(): Mock { const request = mockRequest("OPEN", testData); mockOwner(request, ALICE_ACCOUNT); mockRequester(request, REQUESTER); - request.setup(instance => instance.removeMetadataItem(ALICE_ACCOUNT, SOME_DATA_NAME_HASH)) + request.setup(instance => instance.removeMetadataItem(ALICE_ACCOUNT, ItIsHash(SOME_DATA_NAME_HASH))) .returns() - request.setup(instance => instance.confirmMetadataItem(SOME_DATA_NAME_HASH)) + request.setup(instance => instance.confirmMetadataItem(ItIsHash(SOME_DATA_NAME_HASH))) .returns() request.setup(instance => instance.addMetadataItem({ name: SOME_DATA_NAME, @@ -424,12 +426,12 @@ async function testAddMetadataForbidden(app: Express) { async function testDeleteMetadataSuccess(app: Express, locRequest: Mock, isVerifiedIssuer: boolean) { await request(app) - .delete(`/api/loc-request/${ REQUEST_ID }/metadata/${ SOME_DATA_NAME_HASH }`) + .delete(`/api/loc-request/${ REQUEST_ID }/metadata/${ SOME_DATA_NAME_HASH.toHex() }`) .expect(200); if(isVerifiedIssuer) { - locRequest.verify(instance => instance.removeMetadataItem(It.Is(account => accountEquals(account, ISSUER)), SOME_DATA_NAME_HASH)); + locRequest.verify(instance => instance.removeMetadataItem(It.Is(account => accountEquals(account, ISSUER)), ItIsHash(SOME_DATA_NAME_HASH))); } else { - locRequest.verify(instance => instance.removeMetadataItem(It.Is(account => accountEquals(account, ALICE_ACCOUNT)), SOME_DATA_NAME_HASH)); + locRequest.verify(instance => instance.removeMetadataItem(It.Is(account => accountEquals(account, ALICE_ACCOUNT)), ItIsHash(SOME_DATA_NAME_HASH))); } } @@ -442,7 +444,7 @@ function mockRequestForLink(): Mock { .returns() request.setup(instance => instance.confirmLink(SOME_LINK_TARGET)) .returns() - request.setup(instance => instance.addLink({ target: SOME_LINK_TARGET, nature: SOME_LINK_NATURE})) + request.setup(instance => instance.addLink({ target: SOME_LINK_TARGET, nature: SOME_LINK_NATURE })) .returns() return request; } @@ -467,7 +469,7 @@ function mockModelForAddLink(container: Container, request: Mock instance.getMetadataItem(SOME_DATA_NAME_HASH)).returns({ submitter: { type: "Polkadot", address: ALICE } } as MetadataItemDescription); - request.setup(instance => instance.confirmMetadataItem(SOME_DATA_NAME_HASH)).returns(); + request.setup(instance => instance.getMetadataItem(ItIsHash(SOME_DATA_NAME_HASH))).returns({ submitter: { type: "Polkadot", address: ALICE } } as MetadataItemDescription); + request.setup(instance => instance.confirmMetadataItem(ItIsHash(SOME_DATA_NAME_HASH))).returns(); mockPolkadotIdentityLoc(repository, false); } diff --git a/test/unit/controllers/locrequest.controller.shared.ts b/test/unit/controllers/locrequest.controller.shared.ts index 7b3850f1..d8249fd5 100644 --- a/test/unit/controllers/locrequest.controller.shared.ts +++ b/test/unit/controllers/locrequest.controller.shared.ts @@ -30,7 +30,7 @@ import { LocRequestService, NonTransactionalLocRequestService } from "../../../s import { DisabledIdenfyService, IdenfyService } from "../../../src/logion/services/idenfy/idenfy.service.js"; import { VoteRepository, VoteAggregateRoot } from "../../../src/logion/model/vote.model.js"; import { AuthenticationService, PolkadotService } from "@logion/rest-api-core"; -import { LocBatch, LogionNodeApiClass, UUID, VerifiedIssuerType } from "@logion/node-api"; +import { Hash, LocBatch, LogionNodeApiClass, UUID, VerifiedIssuerType } from "@logion/node-api"; import { VerifiedIssuerSelectionRepository } from "../../../src/logion/model/verifiedissuerselection.model.js"; import { LocAuthorizationService } from "../../../src/logion/services/locauthorization.service.js"; import { SupportedAccountId, polkadotAccount } from "../../../src/logion/model/supportedaccountid.model.js"; @@ -108,7 +108,7 @@ export function testDataWithType(locType: LocType, draft?: boolean): Partial { it('creates a SOF request for Collection LOC', async () => { const factory = new Mock(); const LOC_ID = new UUID("ebf12a7a-f25f-4830-bd91-d0a7051f641e"); - const itemId = "0x6dec991b1b61b44550769ae3c4b7f54f7cd618391f32bab2bc4e3a96cbb2b198"; + const itemId = Hash.fromHex("0x6dec991b1b61b44550769ae3c4b7f54f7cd618391f32bab2bc4e3a96cbb2b198"); const app = setupApp(LocRequestController, container => mockModelForCreateSofRequest(container, factory, 'Collection', LOC_ID, itemId)) await request(app) .post(`/api/loc-request/sof`) .send({ locId: LOC_ID.toString(), - itemId: itemId + itemId: itemId.toHex(), }) .expect(200) .expect('Content-Type', /application\/json/) @@ -70,7 +71,7 @@ describe('LocRequestController - SoF -', () => { }) }) -function mockModelForCreateSofRequest(container: Container, factory: Mock, locType: LocType, locId: UUID, itemId?: string) { +function mockModelForCreateSofRequest(container: Container, factory: Mock, locType: LocType, locId: UUID, itemId?: Hash) { const { request, repository, collectionRepository, loc } = buildMocksForUpdate(container, { factory }); const targetLoc = mockRequest("CLOSED", testDataWithUserIdentityWithType(locType)); @@ -90,8 +91,8 @@ function mockModelForCreateSofRequest(container: Container, factory: Mock(); collectionItem.setup(instance => instance.itemId) - .returns(itemId); - collectionRepository.setup(instance => instance.findBy(locId.toString(), itemId)) + .returns(itemId.toHex()); + collectionRepository.setup(instance => instance.findBy(locId.toString(), ItIsHash(itemId))) .returns(Promise.resolve(collectionItem.object())); } diff --git a/test/unit/controllers/lofile.controller.spec.ts b/test/unit/controllers/lofile.controller.spec.ts index c825af65..c554c229 100644 --- a/test/unit/controllers/lofile.controller.spec.ts +++ b/test/unit/controllers/lofile.controller.spec.ts @@ -15,6 +15,7 @@ import { LoFileService, NonTransactionalLoFileService } from "../../../src/logio import { ALICE, BOB } from "../../helpers/addresses.js"; import { LegalOfficerSettingId } from "../../../src/logion/model/legalofficer.model.js"; import { mockAuthenticatedUser, mockAuthenticationWithAuthenticatedUser } from "@logion/rest-api-core/dist/TestApp.js"; +import { Hash } from "@logion/node-api"; const existingFile: LoFileDescription = { id: 'file1', @@ -79,9 +80,10 @@ describe("LoFileController", () => { const mock = mockAuthenticationForUserOrLegalOfficer(true); const app = setupApp(LoFileController, mockModel, mock); + const wrongHash = Hash.of("wrong-hash").toHex(); await request(app) .put(`/api/lo-file/${ ALICE }/${ existingFile.id }`) - .field({ "hash": "wrong-hash" }) + .field({ "hash": wrongHash }) .attach('file', buffer, { filename: "file-name", contentType: 'text/plain' }) .expect(400); diff --git a/test/unit/controllers/records.controller.spec.ts b/test/unit/controllers/records.controller.spec.ts index 8caa1e7e..5b139c65 100644 --- a/test/unit/controllers/records.controller.spec.ts +++ b/test/unit/controllers/records.controller.spec.ts @@ -1,7 +1,7 @@ import { TestApp } from "@logion/rest-api-core"; import { Container } from "inversify"; import { Mock, It } from "moq.ts"; -import { TypesTokensRecord as ChainTokensRecord, TypesTokensRecordFile as ChainTokensRecordFile, hashString } from "@logion/node-api"; +import { TypesTokensRecord as ChainTokensRecord, TypesTokensRecordFile as ChainTokensRecordFile, Hash } from "@logion/node-api"; import { writeFile } from "fs/promises"; import moment from "moment"; import request from "supertest"; @@ -25,23 +25,24 @@ import { import { GetTokensRecordFileParams, GetTokensRecordParams, LogionNodeTokensRecordService, NonTransactionalTokensRecordService, TokensRecordService } from "../../../src/logion/services/tokensrecord.service.js"; import { LocAuthorizationService } from "../../../src/logion/services/locauthorization.service.js"; import { CollectionItemAggregateRoot, CollectionItemDescription, CollectionRepository } from "../../../src/logion/model/collection.model.js"; +import { ItIsHash } from "../../helpers/Mock.js"; const collectionLocId = "d61e2e12-6c06-4425-aeee-2a0e969ac14e"; const collectionLocOwner = ALICE; const collectionRequester = "5EBxoSssqNo23FvsDeUxjyQScnfEiGxJaNwuwqBH2Twe35BX"; -const itemId = "0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"; +const itemId = Hash.fromHex("0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"); const timestamp = moment(); const ITEM_TOKEN_OWNER = "0x900edc98db53508e6742723988B872dd08cd09c3"; -const recordId = "0x59772b9c70d6cc244274937445f7c5b56ec6fe0a11292c4ed68848655515a1e6"; +const recordId = Hash.fromHex("0x59772b9c70d6cc244274937445f7c5b56ec6fe0a11292c4ed68848655515a1e6"); const recordSubmitter = "5FniDvPw22DMW1TLee9N8zBjzwKXaKB2DcvZZCQU5tjmv1kb"; const SOME_DATA = 'some data'; -const SOME_DATA_HASH = '0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'; +const SOME_DATA_HASH = Hash.fromHex('0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee'); const FILE_NAME = "'a-file.pdf'"; const CID = "cid-784512"; const CONTENT_TYPE = "text/plain"; -const DELIVERY_HASH = '0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2'; +const DELIVERY_HASH = Hash.fromHex('0xf35e4bcbc1b0ce85af90914e04350cce472a2f01f00c0f7f8bc5c7ba04da2bf2'); const { setupApp, mockAuthenticationWithAuthenticatedUser, mockAuthenticatedUser } = TestApp; @@ -65,14 +66,14 @@ describe("TokensRecordController", () => { const app = setupApp(TokensRecordController, container => mockModelForGet(container, true)); await request(app) - .get(`/api/records/${ collectionLocId }/record/${ recordId }`) + .get(`/api/records/${ collectionLocId }/record/${ recordId.toHex() }`) .send() .expect(200) .then(response => { expect(response.body.collectionLocId).toEqual(collectionLocId) - expect(response.body.recordId).toEqual(recordId) + expect(response.body.recordId).toEqual(recordId.toHex()) expect(response.body.addedOn).toEqual(timestamp.toISOString()) - expect(response.body.files[0].hash).toEqual(SOME_DATA_HASH) + expect(response.body.files[0].hash).toEqual(SOME_DATA_HASH.toHex()) }) }) @@ -81,12 +82,12 @@ describe("TokensRecordController", () => { const app = setupApp(TokensRecordController, container => mockModelForGet(container, false)); await request(app) - .get(`/api/records/${ collectionLocId }/record/${ recordId }`) + .get(`/api/records/${ collectionLocId }/record/${ recordId.toHex() }`) .send() .expect(200) .then(response => { expect(response.body.collectionLocId).toEqual(collectionLocId) - expect(response.body.recordId).toEqual(recordId) + expect(response.body.recordId).toEqual(recordId.toHex()) expect(response.body.addedOn).toBeUndefined() expect(response.body.files).toEqual([]) }) @@ -96,12 +97,13 @@ describe("TokensRecordController", () => { const app = setupApp(TokensRecordController, container => mockModelForGet(container, false)); + const nonExistant = Hash.of("non-existent"); await request(app) - .get(`/api/records/${ collectionLocId }/record/0x12345`) + .get(`/api/records/${ collectionLocId }/record/${ nonExistant.toHex() }`) .send() .expect(400) .then(response => { - expect(response.body.errorMessage).toEqual("Tokens Record d61e2e12-6c06-4425-aeee-2a0e969ac14e/0x12345 not found") + expect(response.body.errorMessage).toEqual(`Tokens Record d61e2e12-6c06-4425-aeee-2a0e969ac14e/${ nonExistant } not found`) }) }) @@ -114,8 +116,8 @@ describe("TokensRecordController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(200); }) @@ -128,14 +130,15 @@ describe("TokensRecordController", () => { filePublished: true, })); const buffer = Buffer.from(SOME_DATA); + const wrongHash = Hash.of("wrong-hash").toHex(); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: "wrong-hash" }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: wrongHash }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) .then(response => { - expect(response.body.errorMessage).toBe("Received hash wrong-hash does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"); + expect(response.body.errorMessage).toBe(`Received hash ${ wrongHash } does not match 0x1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee`); }); }) @@ -148,8 +151,8 @@ describe("TokensRecordController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -167,8 +170,8 @@ describe("TokensRecordController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -186,8 +189,8 @@ describe("TokensRecordController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: FILE_NAME }) .expect(400) .expect('Content-Type', /application\/json/) @@ -205,8 +208,8 @@ describe("TokensRecordController", () => { })); const buffer = Buffer.from(SOME_DATA); await request(app) - .post(`/api/records/${ collectionLocId }/${ recordId }/files`) - .field({ hash: SOME_DATA_HASH }) + .post(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files`) + .field({ hash: SOME_DATA_HASH.toHex() }) .attach('file', buffer, { filename: "WrongName.pdf" }) .expect(400) .expect('Content-Type', /application\/json/) @@ -215,7 +218,7 @@ describe("TokensRecordController", () => { }); }); - const downloadUrl = `/api/records/${ collectionLocId }/${ recordId }/files/${ SOME_DATA_HASH }/${ itemId }`; + const downloadUrl = `/api/records/${ collectionLocId }/${ recordId.toHex() }/files/${ SOME_DATA_HASH.toHex() }/${ itemId.toHex() }`; it('fails to download non-existing file', async () => { const app = setupApp(TokensRecordController, container => mockModel(container, { @@ -305,10 +308,10 @@ describe("TokensRecordController", () => { isOwner: true, })); await request(app) - .get(`/api/records/${ collectionLocId }/${ recordId }/deliveries`) + .get(`/api/records/${ collectionLocId }/${ recordId.toHex() }/deliveries`) .expect(200) .expect('Content-Type', /application\/json/) - .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH][0])); + .then(response => expectDeliveryInfo(response.body[SOME_DATA_HASH.toHex()][0])); }) it('downloads existing file source given its hash and is owner', async () => { @@ -323,7 +326,7 @@ describe("TokensRecordController", () => { const filePath = TokensRecordController.tempFilePath({ collectionLocId, recordId, hash: SOME_DATA_HASH}); await writeFile(filePath, SOME_DATA); await request(app) - .get(`/api/records/${ collectionLocId }/${ recordId }/files-sources/${ SOME_DATA_HASH }`) + .get(`/api/records/${ collectionLocId }/${ recordId.toHex() }/files-sources/${ SOME_DATA_HASH.toHex() }`) .expect(200, SOME_DATA) .expect('Content-Type', /text\/plain/); let fileReallyExists = true; @@ -355,12 +358,12 @@ function mockModel( const collectionLoc = new LocRequestAggregateRoot(); collectionLoc.id = collectionLocId; tokensRecord.collectionLocId = collectionLocId; - tokensRecord.recordId = recordId; + tokensRecord.recordId = recordId.toHex(); tokensRecord.addedOn = timestamp.toDate(); const tokensRecordFile = new TokensRecordFile(); tokensRecordFile.collectionLocId = collectionLocId; - tokensRecordFile.recordId = recordId; - tokensRecordFile.hash = SOME_DATA_HASH; + tokensRecordFile.recordId = recordId.toHex(); + tokensRecordFile.hash = SOME_DATA_HASH.toHex(); tokensRecordFile.name = FILE_NAME; tokensRecordFile.contentType = CONTENT_TYPE; tokensRecordFile.tokenRecord = tokensRecord; @@ -371,12 +374,12 @@ function mockModel( const tokensRecordRepository = new Mock() const locRequestRepository = new Mock(); if (recordAlreadyInDB) { - tokensRecordRepository.setup(instance => instance.findBy(collectionLocId, recordId)) + tokensRecordRepository.setup(instance => instance.findBy(collectionLocId, ItIsHash(recordId))) .returns(Promise.resolve(tokensRecord)) tokensRecordRepository.setup(instance => instance.findAllBy(collectionLocId)) .returns(Promise.resolve([ tokensRecord ])) } else { - tokensRecordRepository.setup(instance => instance.findBy(collectionLocId, recordId)) + tokensRecordRepository.setup(instance => instance.findBy(collectionLocId, ItIsHash(recordId))) .returns(Promise.resolve(null)) tokensRecordRepository.setup(instance => instance.findAllBy(collectionLocId)) .returns(Promise.resolve([])) @@ -385,29 +388,29 @@ function mockModel( const delivered: TokensRecordFileDelivered = { collectionLocId, - recordId, - hash: SOME_DATA_HASH, - deliveredFileHash: DELIVERY_HASH, + recordId: recordId.toHex(), + hash: SOME_DATA_HASH.toHex(), + deliveredFileHash: DELIVERY_HASH.toHex(), generatedOn: new Date(), owner: ITEM_TOKEN_OWNER, tokensRecordFile: tokensRecordFile, }; tokensRecordRepository.setup(instance => instance.findLatestDelivery( - It.Is<{ collectionLocId: string, recordId: string, fileHash: string }>(query => + It.Is<{ collectionLocId: string, recordId: Hash, fileHash: Hash }>(query => query.collectionLocId === collectionLocId - && query.recordId === recordId - && query.fileHash === SOME_DATA_HASH + && query.recordId.equalTo(recordId) + && query.fileHash.equalTo(SOME_DATA_HASH) )) ).returnsAsync(delivered); tokensRecordRepository.setup(instance => instance.findLatestDeliveries( - It.Is<{ collectionLocId: string, recordId: string }>(query => + It.Is<{ collectionLocId: string, recordId: Hash }>(query => query.collectionLocId === collectionLocId - && query.recordId === recordId + && query.recordId.equalTo(recordId) )) ).returnsAsync({ - [ SOME_DATA_HASH ]: [ delivered ] + [ SOME_DATA_HASH.toHex() ]: [ delivered ] }); container.bind(TokensRecordRepository).toConstantValue(tokensRecordRepository.object()) @@ -447,18 +450,18 @@ function mockModel( } publishedCollectionItem.setup(instance => instance.getDescription()).returns(itemDescripiton); const collectionRepository = new Mock(); - collectionRepository.setup(instance => instance.findBy(collectionLocId, itemId)) + collectionRepository.setup(instance => instance.findBy(collectionLocId, ItIsHash(itemId))) .returns(Promise.resolve(publishedCollectionItem.object())); container.bind(CollectionRepository).toConstantValue(collectionRepository.object()); const publishedTokensRecordFile: ChainTokensRecordFile = { hash: SOME_DATA_HASH, - name: hashString(FILE_NAME), - contentType: hashString(CONTENT_TYPE), + name: Hash.of(FILE_NAME), + contentType: Hash.of(CONTENT_TYPE), size: SOME_DATA.length.toString(), }; const publishedTokensRecord: ChainTokensRecord = { - description: hashString("Item Description"), + description: Hash.of("Item Description"), files: [], submitter: recordSubmitter, } @@ -467,11 +470,11 @@ function mockModel( } const logionNodeTokensRecordService = new Mock(); logionNodeTokensRecordService.setup(instance => instance.getTokensRecord(It.Is( - param => param.collectionLocId === collectionLocId && param.recordId === recordId) + param => param.collectionLocId === collectionLocId && param.recordId.equalTo(recordId)) )) .returns(Promise.resolve(recordPublished ? publishedTokensRecord : undefined)); logionNodeTokensRecordService.setup(instance => instance.getTokensRecordFile(It.Is( - param => param.collectionLocId === collectionLocId && param.recordId === recordId && param.hash === SOME_DATA_HASH) + param => param.collectionLocId === collectionLocId && param.recordId.equalTo(recordId) && param.hash.equalTo(SOME_DATA_HASH)) )) .returns(Promise.resolve(filePublished ? publishedTokensRecordFile : undefined)); container.bind(LogionNodeTokensRecordService).toConstantValue(logionNodeTokensRecordService.object()); @@ -493,7 +496,7 @@ function mockModel( } function expectDeliveryInfo(responseBody: any) { - expect(responseBody.copyHash).toBe(DELIVERY_HASH); + expect(responseBody.copyHash).toBe(DELIVERY_HASH.toHex()); expect(responseBody.generatedOn).toBeDefined(); expect(responseBody.owner).toBe(ITEM_TOKEN_OWNER); } diff --git a/test/unit/lib/crypto/hashing.spec.ts b/test/unit/lib/crypto/hashing.spec.ts index 094f2d88..60dc0b08 100644 --- a/test/unit/lib/crypto/hashing.spec.ts +++ b/test/unit/lib/crypto/hashing.spec.ts @@ -1,4 +1,5 @@ -import { sha256, sha256File, HashTransformer, sha256String } from '../../../../src/logion/lib/crypto/hashing.js'; +import { sha256, sha256File, HashTransformer } from '../../../../src/logion/lib/crypto/hashing.js'; +import { Hash } from "@logion/node-api"; describe('HashingTest', () => { @@ -20,20 +21,20 @@ describe('HashingTest', () => { it("hashes text file", async () => { const hash = await sha256File("test/unit/lib/crypto/file.txt"); - expect(hash).toBe("0x0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8"); + expect(hash.toHex()).toBe("0x0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8"); }); it("hashes binary file", async () => { const hash = await sha256File("test/unit/lib/crypto/assets.png"); - expect(hash).toBe("0x68a87ced4573656b101940c90ac3bdc24b651f688c06e71c70d37f43dfc5058c"); + expect(hash.toHex()).toBe("0x68a87ced4573656b101940c90ac3bdc24b651f688c06e71c70d37f43dfc5058c"); }); it("convert from/to Buffer", () => { const transformer = HashTransformer.instance; - const hash = sha256String("abc123"); + const hash = Hash.of("abc123"); const buffer = transformer.to(hash); expect(buffer.length).toEqual(32); - expect(transformer.from(buffer)).toEqual(hash); + expect(transformer.from(buffer)?.toHex()).toBe(hash.toHex()); }); }); diff --git a/test/unit/model/collection.model.spec.ts b/test/unit/model/collection.model.spec.ts index 2a474e47..b03bb6e9 100644 --- a/test/unit/model/collection.model.spec.ts +++ b/test/unit/model/collection.model.spec.ts @@ -1,8 +1,9 @@ +import { Hash } from "@logion/node-api"; import { CollectionFactory, CollectionItemAggregateRoot } from "../../../src/logion/model/collection.model.js"; import moment from "moment"; const collectionLocId = "d61e2e12-6c06-4425-aeee-2a0e969ac14e"; -const itemId = "0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"; +const itemId = Hash.fromHex("0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"); const addedOn = moment(); describe("CollectionFactory", () => { @@ -14,8 +15,8 @@ describe("CollectionFactory", () => { itemId, }); expect(collectionItemAggregateRoot.collectionLocId).toEqual(collectionLocId) - expect(collectionItemAggregateRoot.itemId).toEqual(itemId) - expect(collectionItemAggregateRoot.hasFile("unknown")).toBeFalse(); + expect(collectionItemAggregateRoot.itemId).toEqual(itemId.toHex()) + expect(collectionItemAggregateRoot.hasFile(Hash.of("unknown"))).toBeFalse(); }) }) @@ -25,7 +26,7 @@ describe("CollectionItemAggregateRoot", () => { const collectionItemAggregateRoot = new CollectionItemAggregateRoot(); collectionItemAggregateRoot.collectionLocId = collectionLocId; - collectionItemAggregateRoot.itemId = itemId; + collectionItemAggregateRoot.itemId = itemId.toHex(); collectionItemAggregateRoot.addedOn = addedOn.toDate(); const description = collectionItemAggregateRoot.getDescription(); diff --git a/test/unit/model/locrequest.model.spec.ts b/test/unit/model/locrequest.model.spec.ts index dbfca85d..133c9f79 100644 --- a/test/unit/model/locrequest.model.spec.ts +++ b/test/unit/model/locrequest.model.spec.ts @@ -28,7 +28,7 @@ import { import { UUID } from "@logion/node-api"; import { IdenfyVerificationSession, IdenfyVerificationStatus } from "src/logion/services/idenfy/idenfy.types.js"; import { SupportedAccountId } from "../../../src/logion/model/supportedaccountid.model.js"; -import { sha256String, Hash } from "../../../src/logion/lib/crypto/hashing.js"; +import { Hash } from "../../../src/logion/lib/crypto/hashing.js"; const SUBMITTER: SupportedAccountId = { type: "Polkadot", @@ -36,7 +36,7 @@ const SUBMITTER: SupportedAccountId = { }; const PUBLIC_SEAL: PublicSeal = { - hash: "0x48aedf4e08e46b24970d97db566bfa6668581cc2f37791bac0c9817a4508607a", + hash: Hash.fromHex("0x48aedf4e08e46b24970d97db566bfa6668581cc2f37791bac0c9817a4508607a"), version: 0, } @@ -475,7 +475,7 @@ describe("LocRequestAggregateRoot (metadata)", () => { } ]; whenAddingMetadata(items, false); - whenRemovingMetadataItem(remover, sha256String(items[1].name)) + whenRemovingMetadataItem(remover, Hash.of(items[1].name)) const newItems: MetadataItemParams[] = [ { @@ -484,7 +484,7 @@ describe("LocRequestAggregateRoot (metadata)", () => { submitter: SUBMITTER, } ]; - const nameHash1 = sha256String(items[0].name); + const nameHash1 = Hash.of(items[0].name); thenExposesMetadata(newItems); thenExposesMetadataItemByNameHash(nameHash1, { ...newItems[0], nameHash: nameHash1 }); thenHasMetadataItem(nameHash1); @@ -496,7 +496,7 @@ describe("LocRequestAggregateRoot (metadata)", () => { it("confirms metadata item", () => { givenRequestWithStatus('OPEN'); const name = "target-1"; - const nameHash = sha256String(name); + const nameHash = Hash.of(name); whenAddingMetadata([ { name, @@ -620,7 +620,7 @@ describe("LocRequestAggregateRoot (files)", () => { givenRequestWithStatus('OPEN'); const files: FileParams[] = [ { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -630,7 +630,7 @@ describe("LocRequestAggregateRoot (files)", () => { size: 123, }, { - hash: "hash2", + hash: hash2, name: "name2", contentType: "text/plain", cid: "cid-4567", @@ -642,17 +642,17 @@ describe("LocRequestAggregateRoot (files)", () => { ]; whenAddingFiles(files, false); thenExposesFiles(files); - thenExposesFileByHash("hash1", files[0]); - thenExposesFileByHash("hash2", files[1]); - thenHasFile("hash1"); - thenHasFile("hash2"); + thenExposesFileByHash(hash1, files[0]); + thenExposesFileByHash(hash2, files[1]); + thenHasFile(hash1); + thenHasFile(hash2); }); it("does not accept several files with same hash", () => { givenRequestWithStatus('OPEN'); const files: FileParams[] = [ { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", oid: 1234, @@ -662,7 +662,7 @@ describe("LocRequestAggregateRoot (files)", () => { size: 123, }, { - hash: "hash1", + hash: hash1, name: "name2", contentType: "text/plain", oid: 4567, @@ -681,7 +681,7 @@ describe("LocRequestAggregateRoot (files)", () => { givenRequestWithStatus('OPEN'); const files: FileParams[] = [ { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -691,7 +691,7 @@ describe("LocRequestAggregateRoot (files)", () => { size: 123, }, { - hash: "hash2", + hash: hash2, name: "name2", contentType: "text/plain", cid: "cid-4567", @@ -702,12 +702,12 @@ describe("LocRequestAggregateRoot (files)", () => { } ]; whenAddingFiles(files, false); - whenRemovingFile(remover, "hash1"); + whenRemovingFile(remover, hash1); thenReturnedRemovedFile(files[0]); const newFiles: FileParams[] = [ { - hash: "hash2", + hash: hash2, name: "name2", contentType: "text/plain", cid: "cid-4567", @@ -718,8 +718,8 @@ describe("LocRequestAggregateRoot (files)", () => { } ]; thenExposesFiles(newFiles); - thenExposesFileByHash("hash2", newFiles[0]); - thenHasFile("hash2"); + thenExposesFileByHash(hash2, newFiles[0]); + thenHasFile(hash2); thenHasExpectedFileIndices(); } @@ -727,7 +727,7 @@ describe("LocRequestAggregateRoot (files)", () => { it("confirms file", () => { givenRequestWithStatus('OPEN'); - const hash = "hash-1"; + const hash = Hash.of("hash-1"); whenAddingFiles([ { hash, @@ -747,7 +747,7 @@ describe("LocRequestAggregateRoot (files)", () => { it("exposes draft, owner-submitted file to requester", () => { givenRequestWithStatus('OPEN'); - const hash = "hash-3"; + const hash = Hash.of("hash-3"); whenAddingFiles([ { hash, @@ -764,10 +764,10 @@ describe("LocRequestAggregateRoot (files)", () => { }) it("accepts delivered files with restricted delivery", () => { - const hash = "hash-1"; + const hash = Hash.of("hash-1"); givenClosedCollectionLocWithFile(hash); - const deliveredFileHash = "hash-2"; + const deliveredFileHash = Hash.of("hash-2"); request.addDeliveredFile({ hash, deliveredFileHash, @@ -775,13 +775,13 @@ describe("LocRequestAggregateRoot (files)", () => { owner: OWNER, }); - const file = request.files?.find(file => file.hash === hash); + const file = request.files?.find(file => file.hash === hash.toHex()); expect(file?.delivered?.length).toBe(1); - expect(file?.delivered![0].hash).toBe(hash); + expect(file?.delivered![0].hash).toBe(hash.toHex()); expect(file?.delivered![0].requestId).toBe(request.id); expect(file?.delivered![0].file).toBe(file); - expect(file?.delivered![0].deliveredFileHash).toBe(deliveredFileHash); + expect(file?.delivered![0].deliveredFileHash).toBe(deliveredFileHash.toHex()); expect(file?.delivered![0].owner).toBe(OWNER); expect(file?.delivered![0].generatedOn).toBeDefined(); @@ -789,10 +789,10 @@ describe("LocRequestAggregateRoot (files)", () => { }) it("accepts delivered files with restricted delivery", () => { - const hash = "hash-1"; + const hash = Hash.of("hash-1"); givenClosedCollectionLocWithFile(hash); - const deliveredFileHash = "hash-2"; + const deliveredFileHash = Hash.of("hash-2"); request.addDeliveredFile({ hash, deliveredFileHash, @@ -800,13 +800,13 @@ describe("LocRequestAggregateRoot (files)", () => { owner: OWNER, }); - const file = request.files?.find(file => file.hash === hash); + const file = request.files?.find(file => file.hash === hash.toHex()); expect(file?.delivered?.length).toBe(1); - expect(file?.delivered![0].hash).toBe(hash); + expect(file?.delivered![0].hash).toBe(hash.toHex()); expect(file?.delivered![0].requestId).toBe(request.id); expect(file?.delivered![0].file).toBe(file); - expect(file?.delivered![0].deliveredFileHash).toBe(deliveredFileHash); + expect(file?.delivered![0].deliveredFileHash).toBe(deliveredFileHash.toHex()); expect(file?.delivered![0].owner).toBe(OWNER); expect(file?.delivered![0].generatedOn).toBeDefined(); @@ -818,8 +818,8 @@ describe("LocRequestAggregateRoot (files)", () => { request.locType = "Transaction"; expect(() => request.addDeliveredFile({ - hash: "hash-1", - deliveredFileHash: "hash-2", + hash: Hash.of("hash-1"), + deliveredFileHash: Hash.of("hash-2"), generatedOn: moment(), owner: OWNER, })).toThrowError("Restricted delivery is only available with Collection LOCs"); @@ -830,8 +830,8 @@ describe("LocRequestAggregateRoot (files)", () => { request.locType = "Collection"; expect(() => request.addDeliveredFile({ - hash: "hash-1", - deliveredFileHash: "hash-2", + hash: Hash.of("hash-1"), + deliveredFileHash: Hash.of("hash-2"), generatedOn: moment(), owner: OWNER, })).toThrowError("Restricted delivery is only possible with closed Collection LOCs"); @@ -841,19 +841,20 @@ describe("LocRequestAggregateRoot (files)", () => { givenRequestWithStatus('CLOSED'); request.locType = "Collection"; + const hash = Hash.of("hash-1"); expect(() => request.addDeliveredFile({ - hash: "hash-1", - deliveredFileHash: "hash-2", + hash, + deliveredFileHash: Hash.of("hash-2"), generatedOn: moment(), owner: OWNER, - })).toThrowError("No file with hash hash-1"); + })).toThrowError(`No file with hash ${ hash.toHex() }`); }) it("updates file", () => { givenRequestWithStatus('OPEN'); request.locType = "Collection"; const file: FileParams = { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -863,17 +864,17 @@ describe("LocRequestAggregateRoot (files)", () => { size: 123, }; whenAddingFiles([ file ], false); - whenUpdatingFile("hash1", false); - thenExposesFileByHash("hash1", { ...file, restrictedDelivery: false }); - whenUpdatingFile("hash1", true); - thenExposesFileByHash("hash1", { ...file, restrictedDelivery: true }); + whenUpdatingFile(hash1, false); + thenExposesFileByHash(hash1, { ...file, restrictedDelivery: false }); + whenUpdatingFile(hash1, true); + thenExposesFileByHash(hash1, { ...file, restrictedDelivery: true }); }); it("fails to update file for Identity LOC", () => { givenRequestWithStatus('OPEN'); request.locType = "Identity"; const file: FileParams = { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -884,13 +885,15 @@ describe("LocRequestAggregateRoot (files)", () => { }; whenAddingFiles([ file ], false); expect(() => { - whenUpdatingFile("hash1", true); + whenUpdatingFile(hash1, true); }).toThrowError("Can change restricted delivery of file only on Collection LOC."); }); +}); -}) +const hash1 = Hash.of("hash1"); +const hash2 = Hash.of("hash2"); -function givenClosedCollectionLocWithFile(hash: string) { +function givenClosedCollectionLocWithFile(hash: Hash) { givenRequestWithStatus('OPEN'); request.locType = "Collection"; request.addFile({ @@ -911,7 +914,7 @@ describe("LocRequestAggregateRoot (synchronization)", () => { it("sets metadata item timestamp", () => { givenRequestWithStatus("OPEN") const dataName = "data-1"; - const dataNameHash = sha256String(dataName); + const dataNameHash = Hash.of(dataName); whenAddingMetadata([{ name: dataName, value: "value-1", @@ -951,7 +954,7 @@ describe("LocRequestAggregateRoot (synchronization)", () => { givenRequestWithStatus("OPEN") const files: FileParams[] = [ { - hash: "hash1", + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -961,7 +964,7 @@ describe("LocRequestAggregateRoot (synchronization)", () => { size: 123, }, { - hash: "hash2", + hash: hash2, name: "name2", contentType: "text/plain", cid: "cid-4567", @@ -973,11 +976,11 @@ describe("LocRequestAggregateRoot (synchronization)", () => { ]; whenAddingFiles(files, true); const addedOn = moment(); - whenSettingFileAddedOn("hash1", addedOn); - thenFileStatusIs("hash1", "PUBLISHED") - thenFileRequiresUpdate("hash1") - thenExposesFileByHash("hash1", { - hash: "hash1", + whenSettingFileAddedOn(hash1, addedOn); + thenFileStatusIs(hash1, "PUBLISHED") + thenFileRequiresUpdate(hash1) + thenExposesFileByHash(hash1, { + hash: hash1, name: "name1", contentType: "text/plain", cid: "cid-1234", @@ -996,7 +999,7 @@ describe("LocRequestAggregateRoot (processes)", () => { // User creates a draft givenRequestWithStatus("DRAFT"); - const fileHash = "hash1"; + const fileHash = Hash.of("hash1"); request.addFile({ hash: fileHash, name: "name1", @@ -1009,7 +1012,7 @@ describe("LocRequestAggregateRoot (processes)", () => { }, false); expect(request.getFiles(SUBMITTER).length).toBe(1); const itemName = "Some name"; - const itemNameHash = sha256String(itemName); + const itemNameHash = Hash.of(itemName); request.addMetadataItem({ name: itemName, value: "Some value", @@ -1069,7 +1072,7 @@ describe("LocRequestAggregateRoot (processes)", () => { // LLO adds other data request.addFile({ - hash: "hash2", + hash: hash2, name: "name2", contentType: "text/plain", oid: 1235, @@ -1078,8 +1081,8 @@ describe("LocRequestAggregateRoot (processes)", () => { restrictedDelivery: false, size: 123, }, true); - request.confirmFile("hash2"); - request.setFileAddedOn("hash2", moment()); // Sync + request.confirmFile(hash2); + request.setFileAddedOn(hash2, moment()); // Sync const target = new UUID().toString(); request.addLink({ @@ -1090,7 +1093,7 @@ describe("LocRequestAggregateRoot (processes)", () => { request.setLinkAddedOn(target, moment()); // Sync const someOtherName = "Some other name"; - const someOtherNameHash = sha256String(someOtherName); + const someOtherNameHash = Hash.of(someOtherName); request.addMetadataItem({ name: someOtherName, value: "Some other value", @@ -1148,7 +1151,7 @@ function thenRequestStatusIs(expectedStatus: LocRequestStatus) { } function thenRequestSealIs(expectedSeal: Seal) { - expect(request.seal?.hash).toEqual(expectedSeal.hash); + expect(request.seal?.hash).toEqual(expectedSeal.hash.toHex()); expect(request.seal?.salt).toEqual(expectedSeal.salt); } @@ -1217,7 +1220,7 @@ function thenRequestCreatedWithDescription(description: LocRequestDescription, e expect(request.decisionOn).toBeUndefined(); } -function whenUpdatingFile(hash: string, restrictedDelivery: boolean) { +function whenUpdatingFile(hash: Hash, restrictedDelivery: boolean) { request.setFileRestrictedDelivery({ hash, restrictedDelivery }); } @@ -1241,11 +1244,11 @@ function expectSameFiles(f1: FileDescription, f2: Partial) { expect(f1.restrictedDelivery).toEqual(f2.restrictedDelivery!); } -function thenExposesFileByHash(hash: string, expectedFile: Partial) { +function thenExposesFileByHash(hash: Hash, expectedFile: Partial) { expectSameFiles(request.getFile(hash), expectedFile); } -function thenHasFile(hash: string) { +function thenHasFile(hash: Hash) { expect(request.hasFile(hash)).toBe(true); } @@ -1313,23 +1316,23 @@ function thenMetadataIsVisibleToRequester(name: string) { expect(request.getMetadataItems(SUBMITTER)[0].name).toEqual(name); } -function whenSettingFileAddedOn(hash: string, addedOn:Moment) { +function whenSettingFileAddedOn(hash: Hash, addedOn:Moment) { request.setFileAddedOn(hash, addedOn); } -function whenConfirmingFile(hash: string) { +function whenConfirmingFile(hash: Hash) { request.confirmFile(hash); } -function thenFileStatusIs(hash: string, status: ItemStatus) { - expect(request.files?.find(file => file.hash === hash)?.status).toEqual(status) +function thenFileStatusIs(hash: Hash, status: ItemStatus) { + expect(request.files?.find(file => file.hash === hash.toHex())?.status).toEqual(status) } -function thenFileRequiresUpdate(hash: string) { - expect(request.files?.find(file => file.hash === hash)?._toUpdate).toBeTrue(); +function thenFileRequiresUpdate(hash: Hash) { + expect(request.files?.find(file => file.hash === hash.toHex())?._toUpdate).toBeTrue(); } -function thenFileIsVisibleToRequester(hash: string) { +function thenFileIsVisibleToRequester(hash: Hash) { expect(request.getFiles(SUBMITTER).length).toEqual(1); expect(request.getFiles(SUBMITTER)[0].hash).toEqual(hash); } @@ -1366,7 +1369,7 @@ function whenRemovingLink(remover: SupportedAccountId, target: string) { request.removeLink(remover, target); } -function whenRemovingFile(remover: SupportedAccountId, hash: string) { +function whenRemovingFile(remover: SupportedAccountId, hash: Hash) { removedFile = request.removeFile(remover, hash); } diff --git a/test/unit/services/locsynchronization.service.spec.ts b/test/unit/services/locsynchronization.service.spec.ts index 0a3bff5d..270bbeeb 100644 --- a/test/unit/services/locsynchronization.service.spec.ts +++ b/test/unit/services/locsynchronization.service.spec.ts @@ -19,7 +19,8 @@ import { VerifiedIssuerSelectionService } from "src/logion/services/verifiedissu import { NonTransactionalTokensRecordService } from "../../../src/logion/services/tokensrecord.service.js"; import { TokensRecordFactory, TokensRecordRepository } from "../../../src/logion/model/tokensrecord.model.js"; import { ALICE } from "../../helpers/addresses.js"; -import { sha256String, Hash } from "../../../src/logion/lib/crypto/hashing.js"; +import { Hash } from "../../../src/logion/lib/crypto/hashing.js"; +import { ItIsHash } from "../../helpers/Mock.js"; describe("LocSynchronizer", () => { @@ -61,7 +62,7 @@ describe("LocSynchronizer", () => { givenLocExtrinsic("addMetadata", { loc_id: locId, item: { - name: METADATA_ITEM_NAME_HASH, + name: METADATA_ITEM_NAME_HASH.toHex(), value: METADATA_ITEM_VALUE, } }); @@ -100,7 +101,7 @@ describe("LocSynchronizer", () => { givenLocExtrinsic("addFile", { loc_id: locId, file: { - hash: FILE_HASH, + hash: FILE_HASH.toHex(), } }); givenLocRequest(); @@ -129,7 +130,7 @@ describe("LocSynchronizer", () => { }); it("adds Collection Item", async () => { - givenLocExtrinsic("addCollectionItem", { collection_loc_id: locId, item_id: itemId}); + givenLocExtrinsic("addCollectionItem", { collection_loc_id: locId, item_id: itemId.toHex()}); givenLocRequest(); givenCollectionItem(); givenCollectionFactory(); @@ -138,7 +139,7 @@ describe("LocSynchronizer", () => { }); it("adds Collection Item with terms and conditions", async () => { - givenLocExtrinsic("addCollectionItemWithTermsAndConditions", { collection_loc_id: locId, item_id: itemId }); + givenLocExtrinsic("addCollectionItemWithTermsAndConditions", { collection_loc_id: locId, item_id: itemId.toHex() }); givenLocRequest(); givenCollectionItem(); givenCollectionFactory(); @@ -168,7 +169,7 @@ describe("LocSynchronizer", () => { it("confirms metadata acknowledged", async () => { givenLocExtrinsic("acknowledgeMetadata", { loc_id: locId, - name: METADATA_ITEM_NAME_HASH, + name: METADATA_ITEM_NAME_HASH.toHex(), }); givenLocRequest(); givenLocRequestExpectsMetadataItemAcknowledged(); @@ -180,7 +181,7 @@ describe("LocSynchronizer", () => { it("confirms file acknowledged", async () => { givenLocExtrinsic("acknowledgeFile", { loc_id: locId, - hash: FILE_HASH, + hash: FILE_HASH.toHex(), }); givenLocRequest(); givenLocRequestExpectsFileAcknowledged(); @@ -194,7 +195,7 @@ const locDecimalUuid = "130084474896785895402627605545662412605"; const locId = locDecimalUuid; const locIdUuid = UUID.fromDecimalStringOrThrow(locDecimalUuid).toString(); const itemIdHex = "0x818f1c9cd44ed4ca11f2ede8e865c02a82f9f8a158d8d17368a6818346899705"; -const itemId = itemIdHex; +const itemId = Hash.fromHex(itemIdHex); const blockTimestamp = moment(); let locRequestRepository: Mock; let collectionFactory: Mock; @@ -229,14 +230,14 @@ function givenLocRequest() { function givenCollectionItem() { collectionItem = new Mock(); collectionItem.setup(instance => instance.confirm(It.IsAny())).returns(); - collectionRepository.setup(instance => instance.findBy(locIdUuid, itemIdHex)).returns(Promise.resolve(collectionItem.object())); + collectionRepository.setup(instance => instance.findBy(locIdUuid, ItIsHash(itemId))).returns(Promise.resolve(collectionItem.object())); collectionRepository.setup(instance => instance.save(collectionItem.object())).returns(Promise.resolve()); } function givenCollectionFactory() { collectionFactory.setup(instance => instance.newItem(It.Is(params => params.collectionLocId === locIdUuid && - params.itemId === itemIdHex && + params.itemId.equalTo(itemId) && params.addedOn !== undefined ))).returns(collectionItem.object()) } @@ -290,11 +291,10 @@ function givenLocRequestExpectsMetadataItemUpdated() { locRequest.setup(instance => instance.setMetadataItemFee(IS_EXPECTED_NAME_HASH, 42n)).returns(undefined); } -const IS_EXPECTED_NAME_HASH = It.Is(nameHash => nameHash === METADATA_ITEM_NAME_HASH) - const METADATA_ITEM_NAME = "name"; -const METADATA_ITEM_NAME_HASH = sha256String(METADATA_ITEM_NAME); +const METADATA_ITEM_NAME_HASH = Hash.of(METADATA_ITEM_NAME); const METADATA_ITEM_VALUE = "value"; +const IS_EXPECTED_NAME_HASH = ItIsHash(METADATA_ITEM_NAME_HASH); function thenMetadataUpdated() { locRequest.verify(instance => instance.setMetadataItemAddedOn(IS_EXPECTED_NAME_HASH, IS_BLOCK_TIME)); @@ -325,16 +325,16 @@ function thenLinkUpdated() { } function givenLocRequestExpectsFileUpdated() { - locRequest.setup(instance => instance.setFileAddedOn(FILE_HASH, IS_BLOCK_TIME)).returns(undefined); - locRequest.setup(instance => instance.setFileFees(FILE_HASH, IS_EXPECTED_FEES, ALICE)).returns(undefined); + locRequest.setup(instance => instance.setFileAddedOn(ItIsHash(FILE_HASH), IS_BLOCK_TIME)).returns(undefined); + locRequest.setup(instance => instance.setFileFees(ItIsHash(FILE_HASH), IS_EXPECTED_FEES, ALICE)).returns(undefined); } -const FILE_HASH = "0x37f1c3d493ad2320d7cc935446c9e094249b5070988820b864b417b708695ed7"; +const FILE_HASH = Hash.fromHex("0x37f1c3d493ad2320d7cc935446c9e094249b5070988820b864b417b708695ed7"); const IS_EXPECTED_FEES = It.Is(fees => fees.inclusionFee === 42n && fees.storageFee === 24n); function thenFileUpdated() { - locRequest.verify(instance => instance.setFileAddedOn(FILE_HASH, IS_BLOCK_TIME)); - locRequest.verify(instance => instance.setFileFees(FILE_HASH, IS_EXPECTED_FEES, ALICE)); + locRequest.verify(instance => instance.setFileAddedOn(ItIsHash(FILE_HASH), IS_BLOCK_TIME)); + locRequest.verify(instance => instance.setFileFees(ItIsHash(FILE_HASH), IS_EXPECTED_FEES, ALICE)); } function givenLocRequestExpectsVoid() { @@ -358,9 +358,9 @@ function thenMetadataAcknowledged() { } function givenLocRequestExpectsFileAcknowledged() { - locRequest.setup(instance => instance.confirmFileAcknowledged(FILE_HASH, IS_BLOCK_TIME)).returns(undefined); + locRequest.setup(instance => instance.confirmFileAcknowledged(ItIsHash(FILE_HASH), IS_BLOCK_TIME)).returns(undefined); } function thenFileAcknowledged() { - locRequest.verify(instance => instance.confirmFileAcknowledged(FILE_HASH, IS_BLOCK_TIME)); + locRequest.verify(instance => instance.confirmFileAcknowledged(ItIsHash(FILE_HASH), IS_BLOCK_TIME)); } diff --git a/test/unit/services/seal.service.spec.ts b/test/unit/services/seal.service.spec.ts index b569a8a0..51637216 100644 --- a/test/unit/services/seal.service.spec.ts +++ b/test/unit/services/seal.service.spec.ts @@ -2,6 +2,7 @@ import { LATEST_SEAL_VERSION, PersonalInfoSealService } from "../../../src/logio import { UserIdentity } from "../../../src/logion/model/useridentity.js"; import { PostalAddress } from "../../../src/logion/model/postaladdress.js"; import { PersonalInfo } from "../../../src/logion/model/personalinfo.model.js"; +import { Hash } from "@logion/node-api"; describe("PersonalInfoSealService", () => { @@ -33,8 +34,8 @@ describe("PersonalInfoSealService", () => { const version = LATEST_SEAL_VERSION; const expectedHashesPerVersion = [ - "0x90e6d447523780d1a048194b939fa95587e52c01b82bf5683b3801729e300c36", - "0xa22772e11382ecc1cdcc6d776b0e6a8ed210aa0e19e6d2b83dce67c3868468e9", + Hash.fromHex("0x90e6d447523780d1a048194b939fa95587e52c01b82bf5683b3801729e300c36"), + Hash.fromHex("0xa22772e11382ecc1cdcc6d776b0e6a8ed210aa0e19e6d2b83dce67c3868468e9"), ]; it("seals with salt", () => { @@ -55,7 +56,7 @@ describe("PersonalInfoSealService", () => { }) it("fails to verify wrong hash", () => { - const wrongHash = "0xab60bc976540a41f830c29c64db4c8442f20724437a078d309f42a958b29af07"; + const wrongHash = Hash.fromHex("0xab60bc976540a41f830c29c64db4c8442f20724437a078d309f42a958b29af07"); expect(sealService.verify( personalInfo, { hash: wrongHash, salt, version }) @@ -85,7 +86,7 @@ describe("PersonalInfoSealService", () => { version, salt, ); - expect(seal.hash).toBe(expectedHashesPerVersion[version]); + expect(seal.hash).toEqual(expectedHashesPerVersion[version]); expect(sealService.verify(personalInfo, seal)).toBeTrue(); } }) diff --git a/yarn.lock b/yarn.lock index dc319e78..6b8834ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1811,9 +1811,9 @@ __metadata: languageName: node linkType: hard -"@logion/node-api@npm:^0.18.0-1": - version: 0.18.0-1 - resolution: "@logion/node-api@npm:0.18.0-1" +"@logion/node-api@npm:^0.18.0-2": + version: 0.18.0-3 + resolution: "@logion/node-api@npm:0.18.0-3" dependencies: "@polkadot/api": ^10.9.1 "@polkadot/util": ^12.3.2 @@ -1821,7 +1821,7 @@ __metadata: "@types/uuid": ^9.0.2 fast-sha256: ^1.3.0 uuid: ^9.0.0 - checksum: 9509c6597ce9f895fc26b4b28d57796209b48c0841d4baab53f02b100de8de7c88c6d7cc199d8a905d37a6ebe093687ac73301da28e198a202660bee421a06d9 + checksum: 787f85d139e58db9cab5853564374d80d52874dc6027891693402106a481157fded3162d60360c89b8f9e4d05241a9ef24bd607e5d3b85b581cc72b0a94f9e77 languageName: node linkType: hard @@ -5891,7 +5891,7 @@ __metadata: resolution: "logion-backend-ts@workspace:." dependencies: "@istanbuljs/nyc-config-typescript": ^1.0.2 - "@logion/node-api": ^0.18.0-1 + "@logion/node-api": ^0.18.0-2 "@logion/node-exiftool": ^2.3.1-3 "@logion/rest-api-core": ^0.4.3 "@polkadot/wasm-crypto": ^7.1.1