Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement processDigitalConformityCredential function #129

Merged
merged 4 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,741 changes: 1,735 additions & 6 deletions app-config.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ P-->>C: Return VC and resolver URL
},
"epcisAggregationEvent": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "EPCISAggregationEvent"],
"type": ["DigitalTraceabilityEvent"],
"renderTemplate": [
{
"type": "html",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
sidebar_position: 53
title: Process Digital Conformity Credential
---

import Disclaimer from '../../\_disclaimer.mdx';

<Disclaimer />

## Description

The `processDigitalConformityCredential` service is responsible for processing a digital conformity credential, issuing a [Verifiable Credential (VC)](https://uncefact.github.io/spec-untp/docs/specification/VerifiableCredentials), uploading it to the [Storage service](/docs/mock-apps/dependent-services/storage-service), registering the link to the stored digital conformity credential with the [Identity Resolver service](/docs/mock-apps/dependent-services/identity-resolution-service). It handles the entire lifecycle of creating and managing a digital conformity credential, from data input to storage and resolution.

## Diagram

```mermaid
sequenceDiagram
participant C as Client
participant P as processDigitalConformityCredential
participant V as VCKit
participant S as Storage
participant D as DLR
C->>P: Call processDigitalConformityCredential(digitalConformityCredential, context)
P->>P: Validate context
P->>P: Extract identifier
P->>V: Issue VC
V-->>P: Return VC
P->>S: Upload VC
S-->>P: Return VC URL
P->>D: Register link resolver
D-->>P: Return resolver URL
P-->>C: Return digital conformity credential VC and resolver URL
```

## Example

```json
{
"name": "processDigitalConformityCredential",
"parameters": [
{
"vckit": {
"vckitAPIUrl": "https://api.vckit.example.com",
"issuer": "did:example:123456789abcdefghi"
},
"digitalConformityCredential": {
"context": [
"https://jargon.sh/user/unece/ConformityCredential/v/0.5.0/artefacts/jsonldContexts/ConformityCredential.jsonld?class=ConformityCredential"
],
"type": ["DigitalConformityCredential"],
"renderTemplate": [
{
"template": "<div><h2>DigitalConformityCredential</h2></div>",
"@type": "WebRenderingTemplate2022"
}
],
"dlrIdentificationKeyType": "gtin",
"dlrLinkTitle": "DigitalConformityCredential",
"dlrVerificationPage": "https://verify.example.com"
},
"storage": {
"url": "https://storage.example.com/upload",
"params": {
"bucket": "bucket-name",
"resultPath": "/url"
}
},
"dlr": {
"dlrAPIUrl": "https://dlr.example.com/api",
"dlrAPIKey": "dlr-api-key-12345",
"namespace": "gs1",
"linkRegisterPath": "/api/resolver"
},
"identifierKeyPath": "/id"
}
]
}
```

## Definitions

| Property | Required | Description | Type |
| --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| vckit | Yes | Configuration for the VCKit service | [VCKit](/docs/mock-apps/common/vckit) |
| digitalConformityCredential | Yes | Configuration for the Digital Conformity Credential | [Credential](/docs/mock-apps/common/credential) |
| storage | Yes | Configuration for storage service | [Storage](/docs/mock-apps/common/storage) |
| dlr | Yes | Configuration for the Digital Link Resolver | [IDR](/docs/mock-apps/common/idr) |
| identifierKeyPath | Yes | JSON path to the identifier in the credential subject or the object for function and arguments of JSON path to construct identifier | [IdentifierKeyPath](/docs/mock-apps/common/identifier-key-path) |
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ P-->>C: Return digital facility record VC and resolver URL
},
"digitalFacilityRecord": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "DigitalFacilityRecord"],
"type": ["DigitalFacilityRecord"],
"renderTemplate": [
{
"template": "<div><h2>DigitalFacilityRecord</h2></div>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ P-->>C: Return digital identity anchor VC and resolver URL
},
"digitalIdentityAnchor": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "DigitalIdentityAnchor"],
"type": ["DigitalIdentityAnchor"],
"renderTemplate": [
{
"template": "<div><h2>DigitalIdentityAnchor</h2></div>",
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/mock-apps/services/process-dpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ P-->>C: Return VC and resolver URL
"@type": "WebRenderingTemplate2022"
}
],
"type": ["VerifiableCredential", " DigitalProductPassport"],
"type": ["DigitalProductPassport"],
"dlrLinkTitle": "Steel Passport",
"dlrIdentificationKeyType": "gtin",
"dlrVerificationPage": "http://localhost:3332/verify"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ P-->>C: Return object event VC and resolver URL
},
"epcisObjectEvent": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "DigitalTraceabilityEvent"],
"type": ["DigitalTraceabilityEvent"],
"renderTemplate": [
{
"template": "<div><h2>Object Event</h2></div>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ P-->>C: Return VC and resolver URL
},
"epcisTransactionEvent": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "EPCISTransactionEvent"],
"type": ["DigitalTraceabilityEvent"],
"renderTemplate": [
{
"type": "html",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ P-->>C: Return EPCIS VC
},
"epcisTransformationEvent": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://gs1.org/voc/"],
"type": ["VerifiableCredential", "EPCISTransformationEvent"],
"type": ["DigitalTraceabilityEvent"],
"renderTemplate": [
{
"type": "html",
Expand Down Expand Up @@ -84,7 +84,7 @@ P-->>C: Return EPCIS VC
},
"dpp": {
"context": ["https://www.w3.org/2018/credentials/v1", "https://schema.org/"],
"type": ["VerifiableCredential", "DigitalProductPassport"],
"type": ["DigitalProductPassport"],
"renderTemplate": [
{
"type": "html",
Expand Down
42 changes: 38 additions & 4 deletions packages/services/src/__tests__/mocks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const contextTransformationEvent = {
dlr: {
dlrAPIUrl: 'http://localhost',
dlrAPIKey: '5555555555555',
namespace: 'gs1',
linkRegisterPath: '/api/resolver',
},
storage: {
url: 'https://storage.example.com',
Expand Down Expand Up @@ -170,7 +172,7 @@ export const transactionEventMock = {
transactionEventDLRMock: `https://example-dlr.com/nlisid/9988776600000?linkType=all`,
transactionVCMock: {
'@context': ['https://example.sh/TransactionEvent.jsonld'],
type: ['VerifiableCredential', 'TransactionEventCredential'],
type: ['TransactionEventCredential'],
issuer: 'did:web:example.com',
credentialSubject: {
sourceParty: { partyID: `https://beef-steak-shop.com/info.json`, name: 'Beef Steak Shop' },
Expand All @@ -194,7 +196,7 @@ export const aggregationEventMock = {
aggregationEventDLRMock: `https://example.com/gtin/9988776600000.json`,
aggregationVCMock: {
'@context': ['https://example.sh/AggregationEvent.jsonld'],
type: ['VerifiableCredential', 'AggregationEventCredential'],
type: ['AggregationEventCredential'],
issuer: 'did:web:example.com',
credentialSubject: {
parentItem: {
Expand All @@ -214,7 +216,7 @@ export const objectEventContext = {
},
epcisObjectEvent: {
context: ['https://www.w3.org/2018/credentials/v1', 'https://gs1.org/voc/'],
type: ['VerifiableCredential', 'ObjectEventCredential'],
type: ['ObjectEventCredential'],
renderTemplate: [
{
template: '<div><h2>Object Event</h2></div>',
Expand Down Expand Up @@ -275,6 +277,8 @@ export const digitalIdentityAnchorContext = {
dlr: {
dlrAPIUrl: 'http://dlr.example.com',
dlrAPIKey: '1234',
namespace: 'gs1',
linkRegisterPath: '/api/resolver',
},
storage: {
url: 'https://storage.example.com',
Expand All @@ -301,6 +305,8 @@ export const digitalFacilityRecordContext = {
dlr: {
dlrAPIUrl: 'http://dlr.example.com',
dlrAPIKey: '1234',
namespace: 'gs1',
linkRegisterPath: '/api/resolver',
},
storage: {
url: 'https://storage.example.com',
Expand All @@ -318,7 +324,7 @@ export const associationEventContext = {
},
epcisAssociationEvent: {
context: ['https://www.w3.org/2018/credentials/v1', 'https://gs1.org/voc/'],
type: ['VerifiableCredential', 'AssociationEventCredential'],
type: ['AssociationEventCredential'],
renderTemplate: [
{
template: '<div><h2>Association Event</h2></div>',
Expand All @@ -343,3 +349,31 @@ export const associationEventContext = {
},
identifierKeyPath: '/id',
};

export const digitalConformityCredentialContext = {
vckit: {
vckitAPIUrl: 'https://vckit.example.com',
issuer: 'did:web:example.com',
},
digitalConformityCredential: {
context: ['https://www.w3.org/2018/credentials/v1'],
renderTemplate: [{ template: '<p>Render dcc template</p>', '@type': 'WebRenderingTemplate2022' }],
type: ['DigitalConformityCredential'],
dlrLinkTitle: 'DigitalConformityCredential',
dlrIdentificationKeyType: 'gtin',
dlrVerificationPage: 'https://web.example.com/verify',
},
dlr: {
dlrAPIUrl: 'http://dlr.example.com',
dlrAPIKey: '1234',
namespace: 'gs1',
linkRegisterPath: '/api/resolver',
},
storage: {
url: 'https://storage.example.com',
params: {
resultPath: '',
},
},
identifierKeyPath: '/id',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as vckitService from '../vckit.service';
import { uploadData } from '../storage.service';
import * as linkResolverService from '../linkResolver.service';
import { Result } from '../types/validateContext';
import * as validateContext from '../validateContext';
import { IDigitalConformityCredentialContext } from '../types';
import { processDigitalConformityCredential } from '../processDigitalConformityCredential.service';
import { digitalConformityCredentialContext as context } from './mocks/constants';

jest.mock('../vckit.service', () => ({
issueVC: jest.fn(),
}));
jest.mock('../storage.service', () => ({
uploadData: jest.fn(),
}));
jest.mock('../linkResolver.service', () => ({
registerLinkResolver: jest.fn(),
createLinkResolver: jest.fn(),
IdentificationKeyType: jest.fn(),
getLinkResolverIdentifier: jest.fn(),
getLinkResolverIdentifierFromURI: jest.fn(),
LinkType: {
verificationLinkType: 'verificationService',
certificationLinkType: 'certificationInfo',
epcisLinkType: 'epcis',
},
}));

describe('processDigitalConformityCredential', () => {
const digitalConformityCredentialData = {
data: {
type: 'DigitalConformityCredential',
id: '0123456789',
name: 'Digital Conformity Credential',
registeredId: '9220664869327',
},
};

it('should process digital conformity credential successfully', async () => {
(vckitService.issueVC as jest.Mock).mockImplementation(() => ({
credentialSubject: { id: 'https://example.com/123' },
}));
(uploadData as jest.Mock).mockResolvedValue('https://exampleStorage.com/vc.json');

jest
.spyOn(validateContext, 'validateDigitalConformityCredentialContext')
.mockReturnValueOnce({ ok: true, value: context } as unknown as Result<IDigitalConformityCredentialContext>);
jest
.spyOn(linkResolverService, 'getLinkResolverIdentifier')
.mockReturnValue({ identifier: '0123456789', qualifierPath: '/' });
jest.spyOn(linkResolverService, 'registerLinkResolver').mockResolvedValue('https://example.com/link-resolver');

const result = await processDigitalConformityCredential(digitalConformityCredentialData, context);

expect(result.vc).toEqual({ credentialSubject: { id: 'https://example.com/123' } });
expect(result.linkResolver).toEqual('https://example.com/link-resolver');
});

it('should throw error when context validation false', async () => {
const invalidContext: any = { ...context };
delete invalidContext.digitalConformityCredential;

jest
.spyOn(validateContext, 'validateDigitalConformityCredentialContext')
.mockReturnValueOnce({ ok: false, value: 'Invalid context' });

expect(
async () => await processDigitalConformityCredential(digitalConformityCredentialData, invalidContext),
).rejects.toThrow('Invalid context');
});

it('should throw error when identifier not found', async () => {
const invalidIdentifierContent = {
...context,
identifierKeyPath: '/invalid',
};

jest
.spyOn(validateContext, 'validateDigitalConformityCredentialContext')
.mockReturnValueOnce({ ok: true, value: context } as unknown as Result<IDigitalConformityCredentialContext>);

expect(
async () => await processDigitalConformityCredential(digitalConformityCredentialData, invalidIdentifierContent),
).rejects.toThrow('Identifier not found');
});

it('should throw error when DigitalConformityCredential data not found', async () => {
const invalidDigitalConformityCredentialData = {
...digitalConformityCredentialData,
data: undefined,
};

jest
.spyOn(validateContext, 'validateDigitalConformityCredentialContext')
.mockReturnValueOnce({ ok: true, value: context } as unknown as Result<IDigitalConformityCredentialContext>);

expect(
async () => await processDigitalConformityCredential(invalidDigitalConformityCredentialData, context),
).rejects.toThrow('digitalConformityCredential data not found');
});
});
1 change: 1 addition & 0 deletions packages/services/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './epcisEvents/index.js';
export * from './processDPP.service.js';
export * from './processDigitalIdentityAnchor.service.js';
export * from './processDigitalFacilityRecord.service.js';
export * from './processDigitalConformityCredential.service.js';
export * from './vckit.service.js';
export * from './linkResolver.service.js';
export * from './storage.service.js';
Expand Down
Loading
Loading