Skip to content

Commit

Permalink
Merge pull request #181 from uncefact/feat/support-multi-version-and-…
Browse files Browse the repository at this point in the history
…extension

* feat: support extension and multiple version of the schema

* feat: enhance relax schema validation for extension

* test: update unit test for services

* refactor: extension configuration data
  • Loading branch information
namhoang1604 authored Dec 11, 2024
2 parents 809f811 + 427c3e9 commit fb818d5
Show file tree
Hide file tree
Showing 7 changed files with 757 additions and 286 deletions.
28 changes: 26 additions & 2 deletions packages/untp-playground/__tests__/app/page.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { toast } from 'sonner';
import { decodeEnvelopedCredential, isEnvelopedProof } from '@/lib/credentialService';
import {
decodeEnvelopedCredential,
isEnvelopedProof,
detectCredentialType,
detectVersion,
} from '@/lib/credentialService';
import { detectExtension, validateCredentialSchema } from '@/lib/schemaValidation';
import { CredentialUploader } from '@/components/CredentialUploader';
import Home from '../../src/app/page';
import Home from '@/app/page';
import { mockCredential } from '../mocks/vc';

// Mock the dependencies
Expand All @@ -15,6 +21,13 @@ jest.mock('sonner', () => ({
jest.mock('@/lib/credentialService', () => ({
isEnvelopedProof: jest.fn(),
decodeEnvelopedCredential: jest.fn(),
detectCredentialType: jest.fn(),
detectVersion: jest.fn(),
}));

jest.mock('@/lib/schemaValidation', () => ({
detectExtension: jest.fn(),
validateCredentialSchema: jest.fn(),
}));

// Mock child components
Expand Down Expand Up @@ -68,6 +81,10 @@ describe('Home Component', () => {

it('handles valid credential upload', async () => {
(isEnvelopedProof as jest.Mock).mockReturnValue(false);
(detectCredentialType as jest.Mock).mockReturnValue('DigitalProductPassport');
(detectVersion as jest.Mock).mockReturnValue('0.5.0');
(detectExtension as jest.Mock).mockReturnValue(undefined);
(validateCredentialSchema as jest.Mock).mockReturnValue({ valid: true });

render(<Home />);

Expand Down Expand Up @@ -108,6 +125,9 @@ describe('Home Component', () => {
);

(isEnvelopedProof as jest.Mock).mockReturnValue(false);
(detectExtension as jest.Mock).mockReturnValue(undefined);
(detectCredentialType as jest.Mock).mockReturnValue('Unknown');
(detectVersion as jest.Mock).mockReturnValue('0.1.0');

render(<Home />);

Expand All @@ -126,6 +146,10 @@ describe('Home Component', () => {

(isEnvelopedProof as jest.Mock).mockReturnValue(true);
(decodeEnvelopedCredential as jest.Mock).mockReturnValue(mockEnvelopedCredential);
(detectCredentialType as jest.Mock).mockReturnValue('DigitalProductPassport');
(detectVersion as jest.Mock).mockReturnValue('0.5.0');
(detectExtension as jest.Mock).mockReturnValue(undefined);
(validateCredentialSchema as jest.Mock).mockReturnValue({ valid: true });

render(<Home />);

Expand Down
168 changes: 168 additions & 0 deletions packages/untp-playground/__tests__/lib/credentialService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {
decodeEnvelopedCredential,
detectCredentialType,
detectVersion,
isEnvelopedProof,
} from '@/lib/credentialService';
import { jwtDecode } from 'jwt-decode';

// Mock jwt-decode
jest.mock('jwt-decode');

describe('credentialService', () => {
describe('decodeEnvelopedCredential', () => {
beforeEach(() => {
(jwtDecode as jest.Mock).mockClear();
});

it('should return original credential if not enveloped', () => {
const credential = {
type: ['DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp/0.5.0'],
};

const result = decodeEnvelopedCredential(credential);
expect(result).toBe(credential);
});

it('should decode JWT from enveloped credential', () => {
const mockDecodedCredential = {
type: ['DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp/0.5.0'],
};

(jwtDecode as jest.Mock).mockReturnValue(mockDecodedCredential);

const envelopedCredential = {
type: 'EnvelopedVerifiableCredential',
id: 'data:application/vc-ld+jwt,eyJhbGciOiJFZERTQSIsIm',
};

const result = decodeEnvelopedCredential(envelopedCredential);
expect(result).toEqual(mockDecodedCredential);
expect(jwtDecode).toHaveBeenCalledWith('eyJhbGciOiJFZERTQSIsIm');
});

it('should handle missing JWT part', () => {
const envelopedCredential = {
type: 'EnvelopedVerifiableCredential',
id: 'data:application/vc+jwt',
};

const result = decodeEnvelopedCredential(envelopedCredential);
expect(result).toBe(envelopedCredential);
});

it('should handle JWT decode errors', () => {
(jwtDecode as jest.Mock).mockImplementation(() => {
throw new Error('Invalid JWT');
});

const envelopedCredential = {
type: 'EnvelopedVerifiableCredential',
id: 'data:application/vc+jwt,invalid-jwt',
};

const result = decodeEnvelopedCredential(envelopedCredential);
expect(result).toBe(envelopedCredential);
});
});

describe('detectCredentialType', () => {
it('should detect DigitalProductPassport', () => {
const credential = {
type: ['VerifiableCredential', 'DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp/0.5.0'],
};

expect(detectCredentialType(credential)).toBe('DigitalProductPassport');
});

it('should detect DigitalLivestockPassport', () => {
const credential = {
type: ['VerifiableCredential', 'DigitalLivestockPassport'],
'@context': ['https://aatp.foodagility.com/vocabulary/aatp/dlp/0.4.0'],
};

expect(detectCredentialType(credential)).toBe('DigitalLivestockPassport');
});

it('should return Unknown for unsupported type', () => {
const credential = {
type: ['VerifiableCredential', 'UnsupportedType'],
'@context': ['https://example.com'],
};

expect(detectCredentialType(credential)).toBe('Unknown');
});
});

describe('detectVersion', () => {
it('should detect version from UNTP context', () => {
const credential = {
type: ['DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp/0.5.0'],
};

expect(detectVersion(credential)).toBe('0.5.0');
});

it('should detect version from custom domain', () => {
const credential = {
type: ['DigitalLivestockPassport'],
'@context': ['https://aatp.foodagility.com/vocabulary/aatp/dlp/0.4.0'],
};

expect(detectVersion(credential, 'aatp.foodagility.com')).toBe('0.4.0');
});

it('should return unknown for missing version', () => {
const credential = {
type: ['DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp'],
};

expect(detectVersion(credential)).toBe('unknown');
});

it('should return unknown for missing context', () => {
const credential = {
type: ['DigitalProductPassport'],
'@context': ['https://example.com'],
};

expect(detectVersion(credential)).toBe('unknown');
});
});

describe('isEnvelopedProof', () => {
it('should detect enveloped proof', () => {
const credential = {
type: 'EnvelopedVerifiableCredential',
id: 'data:application/vc+jwt,eyJhbGciOiJFZERTQSIsIm',
};

expect(isEnvelopedProof(credential)).toBe(true);
});

it('should detect enveloped proof in verifiableCredential', () => {
const credential = {
verifiableCredential: {
type: 'EnvelopedVerifiableCredential',
id: 'data:application/vc+jwt,eyJhbGciOiJFZERTQSIsIm',
},
};

expect(isEnvelopedProof(credential)).toBe(true);
});

it('should return false for non-enveloped credential', () => {
const credential = {
type: ['DigitalProductPassport'],
'@context': ['https://test.uncefact.org/vocabulary/untp/dpp/0.5.0'],
};

expect(isEnvelopedProof(credential)).toBe(false);
});
});
});
Loading

0 comments on commit fb818d5

Please sign in to comment.