Skip to content

Commit

Permalink
Updated with review comments
Browse files Browse the repository at this point in the history
Signed-off-by: Sownak Roy <[email protected]>
  • Loading branch information
sownak committed Dec 12, 2024
1 parent b52a7dc commit c76b131
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 42 deletions.
123 changes: 84 additions & 39 deletions src/controllers/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class ResourceController {
return true;
})
.withMessage('name, type and content are required'),
check('content').exists().isString().withMessage(Messages.Invalid),
check('content').optional().isString().withMessage(Messages.Invalid),
check('name').optional().isString().withMessage(Messages.Invalid),
check('type').optional().isString().withMessage(Messages.Invalid),
check('version').optional().isString().withMessage(Messages.Invalid),
Expand All @@ -63,15 +63,15 @@ export class ResourceController {
check('name').optional().isString().withMessage(Messages.Invalid),
check('type').optional().isString().withMessage(Messages.Invalid),
check('content')
.exists()
.optional()
.isArray()
.custom((value) => {
if (value.length !== 1) return false;
if (typeof value[0] !== 'string') return false;
return true;
})
.withMessage('The content array must be provided and must have exactly one string'),
check('relativeDidUrl').exists().isString().contains('/resources/').withMessage(Messages.InvalidDidUrl),
check('relativeDidUrl').optional().isString().contains('/resources/').withMessage(Messages.InvalidDidUrl),
check('contentOperation')
.optional()
.isArray()
Expand Down Expand Up @@ -190,6 +190,28 @@ export class ResourceController {
});
}
}

// function to get resource by using name and type
public static async checkResourceStatus(
did: string,
name: string,
type: string
): Promise<{ existingResource: any }> {
let existingResource;
let queryString = did + '?resourceName=' + name + '&resourceType=' + type + '&resourceMetadata=true';
let resource = await CheqdResolver(queryString);
if (resource)
if (resource.contentStream) {
let metadata = resource.contentStream.linkedResourceMetadata || [];
if (metadata.length >= 1) {
return {
existingResource: metadata[0],
};
}
}
return { existingResource: existingResource };
}

public async createResource(request: Request, response: Response) {
const result = validationResult(request);
if (!result.isEmpty()) {
Expand All @@ -210,17 +232,16 @@ export class ResourceController {
} = request.body as IResourceCreateRequest;

let resourcePayload: Partial<MsgCreateResourcePayload> = {};

try {
// check if did is registered on the ledger
let resolvedDocument = await CheqdResolver(did);
if (!resolvedDocument?.didDocument || resolvedDocument.didDocumentMetadata.deactivated) {
return response
.status(400)
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.DidNotFound));
} else {
resolvedDocument = resolvedDocument.didDocument;
}

resolvedDocument = resolvedDocument.didDocument;
// Validate and get store data if any
if (jobId) {
const storeData = LocalStore.instance.getResource(jobId);
Expand All @@ -245,6 +266,12 @@ export class ResourceController {
.status(400)
.json(Responses.GetInvalidResourceResponse('', {}, secret, Messages.InvalidContent));
} else {
const checkResource = await ResourceController.checkResourceStatus(did, name, type);
if (checkResource.existingResource) {
return response
.status(400)
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.ResourceExists));
}
jobId = v4();

resourcePayload = {
Expand Down Expand Up @@ -321,46 +348,16 @@ export class ResourceController {
} = request.body as IResourceUpdateRequest;

let resourcePayload: Partial<MsgCreateResourcePayload> = {};
let existingResource;

try {
// check if did is registered on the ledger
let resolvedDocument = await CheqdResolver(did);
if (!resolvedDocument?.didDocument || resolvedDocument.didDocumentMetadata.deactivated) {
return response
.status(400)
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.DidNotFound));
} else {
const didUrlIndex = resolvedDocument.didDocumentMetadata.linkedResourceMetadata.findIndex(
(resource: { resourceURI: string }) => resource.resourceURI === did + relativeDidUrl
);
if (didUrlIndex !== -1) {
existingResource = resolvedDocument.didDocumentMetadata.linkedResourceMetadata[didUrlIndex];
if (existingResource.resourceName !== name || existingResource.resourceType !== type)
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{ id: relativeDidUrl.split('resources/')[1] },
secret,
Messages.InvalidUpdateResource
)
);
} else {
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{ id: relativeDidUrl.split('resources/')[1] },
secret,
Messages.ResourceNotFound
)
);
}
resolvedDocument = resolvedDocument.didDocument;
}

const resolvedDidDocument = resolvedDocument.didDocument;
// Validate and get store data if any
if (jobId) {
const storeData = LocalStore.instance.getResource(jobId);
Expand All @@ -385,6 +382,54 @@ export class ResourceController {
.status(400)
.json(Responses.GetInvalidResourceResponse('', {}, secret, Messages.InvalidContent));
} else {
let existingResource;
const linkedResourceMetadata = resolvedDocument.didDocumentMetadata.linkedResourceMetadata || [];

if (relativeDidUrl) {
// search resource using relativeDidUrl
const didUrlIndex = linkedResourceMetadata.findIndex(
(resource: { resourceURI: string }) => resource.resourceURI === did + relativeDidUrl
);
if (didUrlIndex !== -1) {
// if resource is found using relativeDidUrl
existingResource = linkedResourceMetadata[didUrlIndex];
// passed name and type must match
if (existingResource.resourceName !== name || existingResource.resourceType !== type)
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{ id: relativeDidUrl.split('resources/')[1] },
secret,
Messages.InvalidUpdateResource
)
);
// If resource has a nextVersionId, then return error
if (existingResource.nextVersionId) {
return response
.status(400)
.send(
Responses.GetInvalidResourceResponse(
did,
{},
secret,
'Only latest version of resource can be updated'
)
);
}
}
} else {
// if not relativeDidUrl, find by name and type
const checkResource = await ResourceController.checkResourceStatus(did, name, type);
existingResource = checkResource.existingResource;
}
if (!existingResource) {
return response
.status(400)
.send(Responses.GetInvalidResourceResponse(did, {}, secret, Messages.ResourceNotFound));
}

jobId = v4();

resourcePayload = {
Expand All @@ -408,7 +453,7 @@ export class ResourceController {
.json(
Responses.GetResourceActionSignatureResponse(
jobId,
resolvedDocument.verificationMethod,
resolvedDidDocument.verificationMethod,
did,
resourcePayload
)
Expand Down
1 change: 1 addition & 0 deletions src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum Messages {
SecretValidation = 'Provide either a valid KeyPair or Signature',
InvalidResource = 'Resource Data is invalid',
ResourceNotFound = 'Resource does not exist',
ResourceExists = 'Resource already exists',
InvalidContent = 'Resource Content is invalid',
InvalidUpdateResource = 'Update resource name or type does not match existing resource',
TestnetFaucet = 'sketch mountain erode window enact net enrich smoke claim kangaroo another visual write meat latin bacon pulp similar forum guilt father state erase bright',
Expand Down
23 changes: 23 additions & 0 deletions tests/resource/createResource.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,26 @@ test('resource-create. Send the final request for Resource creation', async ({ r
expect(response.didUrlState.type).toEqual('TextDocument');
expect(response.didUrlState.version).toEqual('1.0');
});

test('resource-create. Fail second create with same name and type', 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(400);

const body = await payload.json();
expect(body.didUrlState).toBeDefined();
expect(body.didUrlState.state).toBeDefined();
expect(body.didUrlState.state).toEqual('failed');
expect(body.didUrlState.description).toEqual('Invalid payload: Resource already exists');
});
91 changes: 88 additions & 3 deletions tests/resource/updateResource.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ test('resource-update. Send the final request for Resource update', async ({ req
],
};

const resourceCreate = await request.post(`/1.0/updateResource`, {
const resourceUpdate = await request.post(`/1.0/updateResource`, {
data: {
did: didPayload.id,
content: ['SGVsbG8gV29ybGQ='],
Expand All @@ -216,8 +216,8 @@ test('resource-update. Send the final request for Resource update', async ({ req
},
},
});
const response = await resourceCreate.json();
expect(resourceCreate.status()).toBe(201);
const response = await resourceUpdate.json();
expect(resourceUpdate.status()).toBe(201);
expect(response.didUrlState).toBeDefined();
expect(response.didUrlState.didUrl).toBeDefined();
expect(response.didUrlState.state).toBeDefined();
Expand All @@ -226,3 +226,88 @@ test('resource-update. Send the final request for Resource update', async ({ req
expect(response.didUrlState.type).toEqual('TextDocument');
expect(response.didUrlState.version).toEqual('2.0');
});

test('resource-update. Resource update without relativeDidUrl', async ({ request }) => {
const payload = await request.post(`/1.0/updateResource`, {
data: {
did: didPayload.id,
name: 'ResourceName',
type: 'TextDocument',
content: ['SGVsbG8gV29ybGQ='],
version: '3.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-update. Send the final update without relativeDidUrl', 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 resourceUpdate = await request.post(`/1.0/updateResource`, {
data: {
did: didPayload.id,
jobId: resourceJobId,
secret: secret,
options: {
network: 'testnet',
},
},
});
const response = await resourceUpdate.json();
expect(resourceUpdate.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('3.0');
});

test('resource-update. Fail Resource update with existing nextVersionId', async ({ request }) => {
const payload = await request.post(`/1.0/updateResource`, {
data: {
did: didPayload.id,
name: 'ResourceName',
type: 'TextDocument',
content: ['SGVsbG8gV29ybGQ='],
version: '4.0',
relativeDidUrl: '/resources/' + resourceId,
options: {
network: 'testnet',
},
},
});

expect(payload.status()).toBe(400);

const body = await payload.json();
expect(body.didUrlState).toBeDefined();
expect(body.didUrlState.state).toBeDefined();
expect(body.didUrlState.state).toEqual('failed');
expect(body.didUrlState.description).toEqual('Invalid payload: Only latest version of resource can be updated');
});
21 changes: 21 additions & 0 deletions tests/resource/validateResource.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,24 @@ test('resource-update. Resource not found', async ({ request }) => {
expect(body.didUrlState).toBeDefined();
expect(body.didUrlState.description).toEqual('Invalid payload: Resource does not exist');
});

test('resource-update. Send wrong name/type without relativeDidUrl', async ({ request }) => {
const payload = await request.post(`/1.0/updateResource`, {
data: {
did: activeDid,
name: 'ResourceName2',
type: 'TextDocument2',
version: '1.0',
content: ['Test Data'],
options: {
network: 'testnet',
},
},
});

expect(payload.status()).toBe(400);

const body = await payload.json();
expect(body.didUrlState).toBeDefined();
expect(body.didUrlState.description).toEqual('Invalid payload: Resource does not exist');
});

0 comments on commit c76b131

Please sign in to comment.