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: support multi version and extension #181

Merged
merged 4 commits into from
Dec 11, 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
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
Loading