From d3a0723d56640ae00d96e36a90f2aaf3b699b647 Mon Sep 17 00:00:00 2001 From: Sownak Roy Date: Mon, 9 Dec 2024 15:16:43 +0000 Subject: [PATCH] added tests Signed-off-by: Sownak Roy --- src/controllers/resource.ts | 45 ++++--- src/helpers/response.ts | 77 ++++++++++- src/types/types.ts | 1 + tests/resource/createResource.spec.ts | 178 ++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 tests/resource/createResource.spec.ts diff --git a/src/controllers/resource.ts b/src/controllers/resource.ts index 66ea246..3aacc87 100644 --- a/src/controllers/resource.ts +++ b/src/controllers/resource.ts @@ -33,13 +33,13 @@ export class ResourceController { check('did').exists().isString().contains('did:cheqd').withMessage(Messages.InvalidDid), check('jobId') .custom((value, { req }) => { - if (!value && !(req.body.name && req.body.type && req.body.data)) return false; + if (!value && !(req.body.name && req.body.type && req.body.content)) return false; return true; }) - .withMessage('name, type and data are required'), + .withMessage('name, type and content are required'), check('name').optional().isString().withMessage(Messages.Invalid), check('type').optional().isString().withMessage(Messages.Invalid), - check('content').optional().isString().withMessage(Messages.Invalid), + check('content').exists().isString().withMessage(Messages.Invalid), check('relativeDidUrl').optional().isString().contains('/resources/').withMessage(Messages.InvalidDidUrl), check('alsoKnownAs').optional().isArray().withMessage(Messages.Invalid), check('alsoKnownAs.*.uri').isString().withMessage(Messages.Invalid), @@ -51,7 +51,7 @@ export class ResourceController { if (!result.isEmpty()) { return response .status(400) - .json(Responses.GetInvalidResourceResponse({}, request.body.secret, result.array()[0].msg)); + .json(Responses.GetInvalidResourceResponseV1({}, request.body.secret, result.array()[0].msg)); } const { did } = request.params; @@ -100,7 +100,7 @@ export class ResourceController { } else if (!data) { return response .status(400) - .json(Responses.GetInvalidResourceResponse({}, secret, Messages.InvalidResource)); + .json(Responses.GetInvalidResourceResponseV1({}, secret, Messages.InvalidResource)); } else { jobId = v4(); @@ -124,7 +124,7 @@ export class ResourceController { return response .status(200) .json( - Responses.GetResourceActionSignatureResponse( + Responses.GetResourceActionSignatureResponseV1( jobId, resolvedDocument.verificationMethod, resourcePayload @@ -136,11 +136,13 @@ export class ResourceController { await CheqdRegistrar.instance.connect(options); const result = await CheqdRegistrar.instance.createResource(signInputs, resourcePayload); if (result.code == 0) { - return response.status(201).json(Responses.GetResourceSuccessResponse(jobId, secret, resourcePayload)); + return response + .status(201) + .json(Responses.GetResourceSuccessResponseV1(jobId, secret, resourcePayload)); } else { return response .status(400) - .json(Responses.GetInvalidResourceResponse(resourcePayload, secret, Messages.InvalidResource)); + .json(Responses.GetInvalidResourceResponseV1(resourcePayload, secret, Messages.InvalidResource)); } } catch (error) { return response.status(500).json({ @@ -160,10 +162,19 @@ export class ResourceController { if (!result.isEmpty()) { return response .status(400) - .json(Responses.GetInvalidResourceResponse({}, request.body.secret, result.array()[0].msg)); + .json(Responses.GetInvalidResourceResponse('', {}, request.body.secret, result.array()[0].msg)); } - let { did, jobId, content, name, type, secret = {}, options = {} } = request.body as IResourceCreateRequest; + let { + did, + jobId, + content, + name, + type, + version, + secret = {}, + options = {}, + } = request.body as IResourceCreateRequest; let resourcePayload: Partial = {}; try { @@ -185,7 +196,7 @@ export class ResourceController { } else if (storeData.state == IState.Finished) { return response.status(201).json({ jobId, - resourceState: { + didUrlState: { resourceId: storeData.resource.id, state: IState.Finished, secret, @@ -199,7 +210,7 @@ export class ResourceController { } else if (!content) { return response .status(400) - .json(Responses.GetInvalidResourceResponse({}, secret, Messages.InvalidContent)); + .json(Responses.GetInvalidResourceResponse('', {}, secret, Messages.InvalidContent)); } else { jobId = v4(); @@ -208,6 +219,7 @@ export class ResourceController { id: v4(), name, resourceType: type, + version: version, data: fromString(content, 'base64'), }; } @@ -224,6 +236,7 @@ export class ResourceController { Responses.GetResourceActionSignatureResponse( jobId, resolvedDocument.verificationMethod, + did, resourcePayload ) ); @@ -233,16 +246,18 @@ export class ResourceController { await CheqdRegistrar.instance.connect(options); const result = await CheqdRegistrar.instance.createResource(signInputs, resourcePayload); if (result.code == 0) { - return response.status(201).json(Responses.GetResourceSuccessResponse(jobId, secret, resourcePayload)); + return response + .status(201) + .json(Responses.GetResourceSuccessResponse(jobId, secret, did, resourcePayload)); } else { return response .status(400) - .json(Responses.GetInvalidResourceResponse(resourcePayload, secret, Messages.InvalidResource)); + .json(Responses.GetInvalidResourceResponse(did, resourcePayload, secret, Messages.InvalidResource)); } } catch (error) { return response.status(500).json({ jobId, - resourceState: { + didUrlState: { state: IState.Failed, reason: Messages.Internal, description: Messages.TryAgain + error, diff --git a/src/helpers/response.ts b/src/helpers/response.ts index 34cd2b9..4949b32 100644 --- a/src/helpers/response.ts +++ b/src/helpers/response.ts @@ -98,7 +98,7 @@ export class Responses { }; } - static GetResourceActionSignatureResponse( + static GetResourceActionSignatureResponseV1( jobId: string, verificationMethod: VerificationMethod[], resource: Partial @@ -129,6 +129,38 @@ export class Responses { }, }; } + static GetResourceActionSignatureResponse( + jobId: string, + verificationMethod: VerificationMethod[], + did: string, + resource: Partial + ) { + const signingRequest = verificationMethod.map((method) => { + return { + kid: method.id, + type: method.type, + alg: 'EdDSA', + serializedPayload: toString( + MsgCreateResourcePayload.encode(MsgCreateResourcePayload.fromPartial(resource)).finish(), + 'base64pad' + ), + }; + }); + + return { + jobId, + didUrlState: { + didUrl: did + '/resources/' + resource.id, + state: IState.Action, + action: IAction.GetSignature, + description: Messages.GetSignature, + signingRequest, + secret: { + signingResponse: [Messages.SigingResponse], + }, + }, + }; + } static GetInvalidResponse(didDocument: DIDDocument | undefined, secret: Record = {}, error: string) { return { @@ -167,7 +199,7 @@ export class Responses { }; } - static GetResourceSuccessResponse( + static GetResourceSuccessResponseV1( jobId: string, secret: Record, resourcePayload: Partial @@ -182,8 +214,29 @@ export class Responses { }, }; } + static GetResourceSuccessResponse( + jobId: string, + secret: Record, + did: string, + resourcePayload: Partial + ) { + return { + jobId, + didUrlState: { + didUrl: did + '/resources/' + resourcePayload.id || '', + state: IState.Finished, + secret, + content: resourcePayload.data, + name: resourcePayload.name, + type: resourcePayload.resourceType, + version: resourcePayload.version, + }, + didRegistrationMetadata: {}, + contentMetadata: {}, + }; + } - static GetInvalidResourceResponse( + static GetInvalidResourceResponseV1( resourcePayload: Partial = {}, secret: Record = {}, error: string @@ -200,4 +253,22 @@ export class Responses { }, }; } + static GetInvalidResourceResponse( + did: string, + resourcePayload: Partial = {}, + secret: Record = {}, + error: string + ) { + return { + jobId: null, + didUrlState: { + didUrl: did + '/resources/' + resourcePayload.id, + state: IState.Failed, + reason: Messages.Invalid, + description: Messages.Invalid + ': ' + error, + secret, + resourcePayload, + }, + }; + } } diff --git a/src/types/types.ts b/src/types/types.ts index 4c6bb23..dc9052a 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -109,6 +109,7 @@ export interface IResourceCreateRequest { content: any; name: string; type: string; + version: string; } export interface IResourceUpdateRequest { jobId: string | null; diff --git a/tests/resource/createResource.spec.ts b/tests/resource/createResource.spec.ts new file mode 100644 index 0000000..90a95b0 --- /dev/null +++ b/tests/resource/createResource.spec.ts @@ -0,0 +1,178 @@ +import { test, expect } from '@playwright/test'; +import { sign } from '@stablelib/ed25519'; +import { toString, fromString } from 'uint8arrays'; +import base64url from 'base64url'; + +import * as dotenv from 'dotenv'; +import { assert } from 'console'; + +dotenv.config(); + +const pub_key_base_64 = process.env.TEST_PUBLIC_KEY; +const priv_key_base_64 = process.env.TEST_PRIVATE_KEY; + +assert(pub_key_base_64, 'TEST_PUBLIC_KEY is not defined'); +assert(priv_key_base_64, 'TEST_PRIVATE_KEY is not defined'); + +const pubKeyHex = toString(fromString(pub_key_base_64 as string, 'base64pad'), 'base16'); +const privKeyBytes = base64url.toBuffer(priv_key_base_64 as string); + +let didPayload; +let didState; +let didUrlState; +let jobId; +let resourceJobId; + +test('did-document. Generate the payload', async ({ request }) => { + const payload = await request.get( + `/1.0/did-document?verificationMethod=JsonWebKey2020&methodSpecificIdAlgo=uuid&network=testnet&publicKeyHex=${pubKeyHex}` + ); + + expect(payload.status()).toBe(200); + + const body = await payload.json(); + expect(body.didDoc).toBeDefined(); + expect(body.key).toBeDefined(); + expect(body.key.kid).toBeDefined(); + expect(body.key.publicKeyHex).toBeDefined(); + + didPayload = body.didDoc; +}); + +test('resource-create. Initiate DID Create procedure', async ({ request }) => { + const payload = await request.post('/1.0/create', { + data: { + didDocument: didPayload, + secret: {}, + options: { + network: 'testnet', + }, + }, + }); + + expect(payload.status()).toBe(200); + + const body = await payload.json(); + + expect(body.jobId).toBeDefined(); + expect(body.didState).toBeDefined(); + expect(body.didState.did).toBeDefined(); + expect(body.didState.state).toBeDefined(); + expect(body.didState.secret).toBeDefined(); + + didState = body.didState; + jobId = body.jobId; +}); + +test('resource-create. Send the final request for DID creating', async ({ request }) => { + const serializedPayload = didState.signingRequest[0].serializedPayload; + const serializedBytes = Buffer.from(serializedPayload, 'base64'); + const signature = sign(privKeyBytes, serializedBytes); + + const secret = { + signingResponse: [ + { + kid: didState.signingRequest[0].kid, + signature: toString(signature, 'base64'), + }, + ], + }; + + const didCreate = await request.post(`/1.0/create`, { + data: { + jobId: jobId, + secret: secret, + options: { + network: 'testnet', + }, + didDocument: didPayload, + }, + }); + + expect(didCreate.status()).toBe(201); +}); + +test('resource-create. Fail to send content', async ({ request }) => { + const payload = await request.post(`/1.0/createResource`, { + data: { + did: didPayload.id, + name: 'ResourceName', + type: 'TextDocument', + version: '1.0', + options: { + network: 'testnet', + }, + }, + }); + + expect(payload.status()).toBe(400); + + const body = await payload.json(); + expect(body.didUrlState).toBeDefined(); + expect(body.didUrlState.description).toEqual('Invalid payload: name, type and content are required'); +}); + +test('resource-create. Initiate Resource creation procedure', async ({ request }) => { + const payload = await request.post(`/1.0/createResource`, { + data: { + did: didPayload.id, + content: 'SGVsbG8gV29ybGQ=', + name: 'ResourceName', + type: 'TextDocument', + version: '1.0', + options: { + network: 'testnet', + }, + }, + }); + + expect(payload.status()).toBe(200); + + const body = await payload.json(); + expect(body.didUrlState).toBeDefined(); + expect(body.didUrlState.didUrl).toBeDefined(); + expect(body.didUrlState.state).toBeDefined(); + expect(body.didUrlState.signingRequest).toBeDefined(); + + didUrlState = body.didUrlState; + resourceJobId = body.jobId; +}); + +test('resource-create. Send the final request for Resource creating', async ({ request }) => { + const serializedPayload = didUrlState.signingRequest[0].serializedPayload; + const serializedBytes = Buffer.from(serializedPayload, 'base64'); + const signature = sign(privKeyBytes, serializedBytes); + + const secret = { + signingResponse: [ + { + kid: didUrlState.signingRequest[0].kid, + signature: toString(signature, 'base64'), + }, + ], + }; + + const resourceCreate = await request.post(`/1.0/createResource`, { + data: { + did: didPayload.id, + content: 'SGVsbG8gV29ybGQ=', + name: 'ResourceName', + type: 'TextDocument', + version: '1.0', + jobId: resourceJobId, + secret: secret, + options: { + network: 'testnet', + }, + }, + }); + const response = await resourceCreate.json(); + expect(resourceCreate.status()).toBe(201); + expect(response.didUrlState).toBeDefined(); + expect(response.didUrlState.didUrl).toBeDefined(); + expect(response.didUrlState.state).toBeDefined(); + expect(response.didUrlState.state).toEqual('finished'); + expect(response.didUrlState.name).toEqual('ResourceName'); + expect(response.didUrlState.type).toEqual('TextDocument'); + expect(response.didUrlState.version).toEqual('1.0'); +});