-
+ ({ content, code, language, codeLanguage, breakLineAt, title }, index) => (
+
+ {content && {content}}
+ {code &&
+
{addLineBreaks(code, breakLineAt)}
-
+ }
),
)}
diff --git a/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/allowListPfi.test.js b/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/allowListPfi.test.js
new file mode 100644
index 000000000..47975a377
--- /dev/null
+++ b/site/testsuites/testsuite-javascript/__tests__/tbdex/wallet/allowListPfi.test.js
@@ -0,0 +1,25 @@
+import { test, expect, describe, beforeAll } from 'vitest';
+import { DidDhtMethod } from '@web5/dids';
+
+let pfiDid;
+
+describe('allowlist PFIs', () => {
+ beforeAll(async () => {
+ pfiDid = await DidDhtMethod.create({
+ publish: false,
+ services: [{
+ id: 'pfi',
+ type: 'PFI',
+ serviceEndpoint: 'tbdex-pfi.tbddev.org'
+ }]
+ })
+ });
+
+ test('PFI DID has PFI service', async () => {
+ // :snippet-start: isPFIJs
+ const isPFI = pfiDid.document.service.some(service => service.type === 'PFI');
+ // :snippet-end:
+ expect(isPFI).toBe(true)
+ });
+
+});
\ No newline at end of file
diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan-club-vc.test.js b/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan-club-vc.test.js
new file mode 100644
index 000000000..3314fc17d
--- /dev/null
+++ b/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/fan-club-vc.test.js
@@ -0,0 +1,175 @@
+import { test, expect, vi, describe, beforeAll } from 'vitest';
+import { DidKeyMethod } from '@web5/dids';
+import { VerifiableCredential, PresentationExchange } from '@web5/credentials';
+import { setUpWeb5 } from '../../../setup-web5';
+
+describe('fan-club-vc', () => {
+ let web5, did, fanClubIssuerDid, aliceDid, SwiftiesFanClub, vc, signedVcJwt, presentationDefinition;
+
+ beforeAll(async () => {
+ await setUpWeb5();
+ web5 = globalThis.web5;
+ did = globalThis.did;
+
+ fanClubIssuerDid = await DidKeyMethod.create();
+ aliceDid = await DidKeyMethod.create();
+
+ SwiftiesFanClub = class {
+ constructor(level, legit) {
+ this.level = level;
+ this.legit = legit;
+ }
+ };
+
+ vc = await VerifiableCredential.create({
+ type: 'SwiftiesFanClub',
+ issuer: fanClubIssuerDid.did,
+ subject: aliceDid.did,
+ data: new SwiftiesFanClub('Stan', true)
+ });
+
+ signedVcJwt = await vc.sign({ did: fanClubIssuerDid });
+
+ presentationDefinition = {
+ 'id': 'presDefId123',
+ 'name': 'Swifties Fan Club Presentation Definition',
+ 'purpose': 'for proving membership in the fan club',
+ 'input_descriptors': [
+ {
+ 'id': 'legitness',
+ 'purpose': 'are you legit or not?',
+ 'constraints': {
+ 'fields': [
+ {
+ 'path': [
+ '$.credentialSubject.legit',
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ };
+
+ vi.mock('@web5/api', () => ({
+ Web5: {
+ connect: vi.fn(() => ({ web5, did })),
+ },
+ }));
+ });
+ test('createDids creates an issuer DID and alice DID with did:key method', async () => {
+ // :snippet-start: createDids
+ const fanClubIssuerDid = await DidKeyMethod.create();
+ const aliceDid = await DidKeyMethod.create();
+ // :snippet-end:
+ expect(aliceDid.did).toMatch(/^did:key:/);
+ expect(fanClubIssuerDid.did).toMatch(/^did:key:/);
+ });
+
+ test('createFanClubVc creates a vc for fan club', async () => {
+ // :snippet-start: createFanClubVc
+ const vc = await VerifiableCredential.create({
+ type: 'SwiftiesFanClub',
+ issuer: fanClubIssuerDid.did,
+ subject: aliceDid.did,
+ data: new SwiftiesFanClub('Stan', true)
+ });
+ // :snippet-end:
+ expect(vc).toHaveProperty('vcDataModel');
+ expect(vc.vcDataModel).toHaveProperty('credentialSubject');
+ expect(vc.vcDataModel.credentialSubject.level).toBe('Stan');
+ expect(vc.vcDataModel.credentialSubject.legit).toBe(true);
+ });
+
+ test('signFanClubVc signs a vc for fan club and returns jwt', async () => {
+ // :snippet-start: signFanClubVc
+ const signedVcJwt = await vc.sign({ did: fanClubIssuerDid });
+ // :snippet-end:
+ expect(typeof signedVcJwt).toBe('string');
+ expect(signedVcJwt).not.toBe('');
+ });
+
+ test('createAndValidatePresentation creates and validates presentation definition', async () => {
+ // :snippet-start: createAndValidatePresentation
+ const presentationDefinition = {
+ 'id': 'presDefId123',
+ 'name': 'Swifties Fan Club Presentation Definition',
+ 'purpose': 'for proving membership in the fan club',
+ 'input_descriptors': [
+ {
+ 'id': 'legitness',
+ 'purpose': 'are you legit or not?',
+ 'constraints': {
+ 'fields': [
+ {
+ 'path': [
+ '$.credentialSubject.legit',
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ };
+
+ const definitionValidation = PresentationExchange.validateDefinition({ presentationDefinition });
+ // :snippet-end:
+
+ expect(Array.isArray(definitionValidation)).toBe(true);
+ expect.soft(definitionValidation[0]).toHaveProperty('status', 'info');
+ expect.soft(definitionValidation[0]).toHaveProperty('message', 'ok');
+ });
+
+ test('satisfiesPresentationDefinitionFanClubVc checks if VC satisfies the presentation definition', async () => {
+ const logSpy = vi.spyOn(console, 'log');
+ // :snippet-start: satisfiesPresentationDefinitionFanClubVc
+ // Does VC Satisfy the Presentation Definition
+ try {
+ PresentationExchange.satisfiesPresentationDefinition({ vcJwts: [signedVcJwt], presentationDefinition: presentationDefinition });
+ console.log('\nVC Satisfies Presentation Definition!\n');
+ } catch (err) {
+ console.log('VC does not satisfy Presentation Definition: ' + err.message);
+ }
+ // :snippet-end:
+ expect(logSpy).toHaveBeenCalledWith('\nVC Satisfies Presentation Definition!\n');
+ logSpy.mockRestore();
+ });
+
+ test('createPresentationFromCredentialsFanClubVc creates presentation from credentials and checks the presentation result', async () => {
+ // :snippet-start: createPresentationFromCredentialsFanClubVc
+ // Create Presentation Result that contains a Verifiable Presentation and Presentation Submission
+ const presentationResult = PresentationExchange.createPresentationFromCredentials({ vcJwts: [signedVcJwt], presentationDefinition: presentationDefinition });
+ console.log('\nPresentation Result: ' + JSON.stringify(presentationResult));
+ // :snippet-end:
+
+ expect(presentationResult.presentation).toHaveProperty('@context');
+ expect(presentationResult.presentation).toHaveProperty('type');
+ expect(presentationResult.presentation).toHaveProperty('presentation_submission');
+ expect(presentationResult).toHaveProperty('presentationSubmissionLocation');
+ expect(presentationResult).toHaveProperty('presentationSubmission');
+ });
+ test('verifyFanClubVc checks if VC verification is successful', async () => {
+ const logSpy = vi.spyOn(console, 'log');
+ // :snippet-start: verifyFanClubVc
+ try {
+ await VerifiableCredential.verify({ vcJwt: signedVcJwt });
+ console.log('\nVC Verification successful!\n');
+ } catch (err) {
+ console.log('\nVC Verification failed: ' + err.message + '\n');
+ }
+ // :snippet-end:
+ expect(logSpy).toHaveBeenCalledWith('\nVC Verification successful!\n');
+ logSpy.mockRestore();
+ });
+
+ test('parseFanClubJwt parses the signed VC JWT', async () => {
+ // :snippet-start: parseFanClubJwt
+ const parsedVC = await VerifiableCredential.parseJwt({ vcJwt: signedVcJwt });
+ // :snippet-end:
+ expect(parsedVC).toHaveProperty('vcDataModel');
+ expect(parsedVC.vcDataModel).toHaveProperty('credentialSubject');
+ expect(parsedVC.vcDataModel.credentialSubject.level).toBe('Stan');
+ expect(parsedVC.vcDataModel.credentialSubject.legit).toBe(true);
+ });
+});
+
diff --git a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js b/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js
index 32ef2ba3a..e46f89ba2 100644
--- a/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js
+++ b/site/testsuites/testsuite-javascript/__tests__/web5/build/verifiable-credentials/presentation-exchange.test.js
@@ -1,24 +1,148 @@
import { test, describe, expect } from 'vitest';
import {
- pex_checkPresentationDefinitionSatisfaction,
pex_createPresentationFromCredentials,
pex_getLoanAppPresentationDefinition,
- pex_getPresentationFromResult,
- pex_selectCredentials,
pex_submissionCheck,
} from '../../../../../../code-snippets/web5/build/verifiable-credentials/presentation-exchange';
+import { PresentationExchange } from '@web5/credentials';
-const signedEmploymentVcJwt =
- 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ';
-const signedNameAndDobVcJwt =
- 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg';
-const credentials = [signedEmploymentVcJwt, signedNameAndDobVcJwt];
const pd = await pex_getLoanAppPresentationDefinition();
describe('Presentation Exchange Process', () => {
- test('selectCredentials() selects VCs that match presentation defintion', async () => {
- const selectedCredentials = await pex_selectCredentials(credentials, pd);
+ const signedEmploymentVcJwt =
+ 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ';
+ const signedNameAndDobVcJwt =
+ 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg';
+ const credentials = [signedEmploymentVcJwt, signedNameAndDobVcJwt];
+
+
+ const presentationDefinition = {
+ id: 'presDefIdloanAppVerification123',
+ name: 'Loan Application Employment Verification',
+ purpose: 'To verify applicant’s employment, date of birth, and name',
+ input_descriptors: [
+ // Employment Verification
+ {
+ id: 'employmentVerification',
+ purpose: 'Confirm current employment status',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.employmentStatus'],
+ filter: {
+ type: 'string',
+ pattern: 'employed',
+ },
+ },
+ ],
+ },
+ },
+ // Date of Birth Verification
+ {
+ id: 'dobVerification',
+ purpose: 'Confirm the applicant’s date of birth',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.dateOfBirth'],
+ filter: {
+ type: 'string',
+ format: 'date',
+ },
+ },
+ ],
+ },
+ },
+ // Name Verification
+ {
+ id: 'nameVerification',
+ purpose: 'Confirm the applicant’s legal name',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.name'],
+ filter: {
+ type: 'string'
+ }
+ }
+ ]
+ }
+ }
+ ]
+ };
+
+ test('getLoanAppPresentationDefinition returns a presentation definition', async () => {
+ // :snippet-start: getLoanAppPresentationDefinition
+ const presentationDefinition = {
+ id: 'presDefIdloanAppVerification123',
+ name: 'Loan Application Employment Verification',
+ purpose: 'To verify applicant’s employment, date of birth, and name',
+ input_descriptors: [
+ // Employment Verification
+ {
+ id: 'employmentVerification',
+ purpose: 'Confirm current employment status',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.employmentStatus'],
+ filter: {
+ type: 'string',
+ pattern: 'employed',
+ },
+ },
+ ],
+ },
+ },
+ // Date of Birth Verification
+ {
+ id: 'dobVerification',
+ purpose: 'Confirm the applicant’s date of birth',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.dateOfBirth'],
+ filter: {
+ type: 'string',
+ format: 'date',
+ },
+ },
+ ],
+ },
+ },
+ // Name Verification
+ {
+ id: 'nameVerification',
+ purpose: 'Confirm the applicant’s legal name',
+ constraints: {
+ fields: [
+ {
+ path: ['$.credentialSubject.name'],
+ filter: {
+ type: 'string'
+ }
+ }
+ ]
+ }
+ }
+ ]
+ };
+ // :snippet-end:
+ expect(presentationDefinition).toBeDefined();
+ expect(presentationDefinition).toHaveProperty('input_descriptors');
+ expect(presentationDefinition.input_descriptors).toBeInstanceOf(Array);
+ expect(presentationDefinition.input_descriptors.length).toBe(3);
+ });
+ test('selectCredentialsForPex selects VCs that match presentation defintion', async () => {
+ const allCredentials = credentials
+ // :snippet-start: selectCredentialsForPex
+ const selectedCredentials = PresentationExchange.selectCredentials({
+ vcJwts: allCredentials,
+ presentationDefinition: presentationDefinition
+ });
+ // :snippet-end:
+
expect(selectedCredentials).toBeDefined();
expect(selectedCredentials).toBeInstanceOf(Array);
expect.soft(selectedCredentials.length).toBe(2);
@@ -26,47 +150,64 @@ describe('Presentation Exchange Process', () => {
expect.soft(selectedCredentials).toContain(signedNameAndDobVcJwt);
});
- test('satisfiesPresentationDefinition() checks if VCs satisfy PD', async () => {
- const satisfied = await pex_checkPresentationDefinitionSatisfaction(
- credentials,
- pd,
- );
- expect(satisfied).toBe(true);
+ test('satisfiesPresentationDefinitionForPex checks if VCs satisfy PD', async () => {
+ const selectedCredentials = credentials
+ expect(() => {
+ // :snippet-start: satisfiesPresentationDefinitionForPex
+ try {
+ PresentationExchange.satisfiesPresentationDefinition({
+ vcJwts: selectedCredentials,
+ presentationDefinition: presentationDefinition
+ });
+ } catch (err) {
+ //Handle errors here
+
+ }
+ // :snippet-end:
+ }).not.toThrow();
});
- test('createPresentationFromCredentials() creates a presentation result', async () => {
- const presentationResult = await pex_createPresentationFromCredentials(
- credentials,
- pd,
- );
+ test('createPresentationFromCredentialsForPex creates a presentation result', async () => {
+ const selectedCredentials = credentials
+ // :snippet-start: createPresentationFromCredentialsForPex
+ const presentationResult = PresentationExchange.createPresentationFromCredentials({
+ vcJwts: selectedCredentials,
+ presentationDefinition: presentationDefinition
+ });
+ // :snippet-end:
expect(presentationResult).toBeDefined();
- expect.soft(presentationResult).toHaveProperty('presentation');
- expect
- .soft(presentationResult.presentation)
- .toHaveProperty('presentation_submission');
- expect
- .soft(presentationResult.presentation)
- .toHaveProperty('verifiableCredential');
- expect
- .soft(presentationResult.presentation.type)
- .toContain('VerifiablePresentation');
- });
+ expect(presentationResult).toHaveProperty('presentation');
+ expect(presentationResult.presentation).toHaveProperty('presentation_submission');
+ expect(presentationResult.presentation).toHaveProperty('verifiableCredential');
+ expect(presentationResult.presentation.type).toContain('VerifiablePresentation');
+ }
+ );
- test('validateSubmission() checks if the presentation submission is valid', async () => {
- const pr = await pex_createPresentationFromCredentials(credentials, pd);
- const validationResult = await pex_submissionCheck(pr);
- expect(validationResult).toBeDefined();
- expect(validationResult).toBeInstanceOf(Array);
- expect(validationResult.length).toBe(1);
- expect.soft(validationResult[0]).toHaveProperty('tag', 'root');
- expect.soft(validationResult[0]).toHaveProperty('status', 'info');
- expect.soft(validationResult[0]).toHaveProperty('message', 'ok');
+ test('validPresentationSubmissionForPex check if the presention submission is valid', async () => {
+ const presentationResult = await pex_createPresentationFromCredentials(credentials, pd);
+ // :snippet-start: validPresentationSubmissionForPex
+ const submissionCheck = PresentationExchange.validateSubmission({
+ presentationSubmission: presentationResult.presentationSubmission
+ });
+ // :snippet-end:
+ expect(submissionCheck.length).toBe(1);
+ expect.soft(submissionCheck[0]).toHaveProperty('tag', 'root');
+ expect.soft(submissionCheck[0]).toHaveProperty('status', 'info');
+ expect.soft(submissionCheck[0]).toHaveProperty('message', 'ok');
});
- test('presentationResult.presentation is a valid VP', async () => {
- const pr = await pex_createPresentationFromCredentials(credentials, pd);
- const presentation = await pex_getPresentationFromResult(pr);
+ test('validVerifiablePresentationForPex creates a valid VP', async () => {
+ const selectedCredentials = credentials
+ const presentationResult = PresentationExchange.createPresentationFromCredentials({
+ vcJwts: selectedCredentials,
+ presentationDefinition: presentationDefinition
+ });
+ // :snippet-start: validVerifiablePresentationForPex
+ const presentation = presentationResult.presentation;
+ // :snippet-end:
+
expect(presentation).toBeDefined();
expect(presentation.type).contains('VerifiablePresentation');
});
+
});
diff --git a/site/testsuites/testsuite-kotlin/pom.xml b/site/testsuites/testsuite-kotlin/pom.xml
index 68aa13ac1..826c6865f 100644
--- a/site/testsuites/testsuite-kotlin/pom.xml
+++ b/site/testsuites/testsuite-kotlin/pom.xml
@@ -25,14 +25,14 @@
These need to be uniformly updated as part of the
single dependency script from Nick
-->
- 0.9.0-SNAPSHOT
+ 0.9.0-beta
- 2.9.8
+ 2.12.6
@@ -94,6 +94,16 @@
jackson-databind
${version.com.fasterxml.jackson.core}
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.12.1
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.12.1
+
diff --git a/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/tbdex/wallet/PfiAllowListTest.kt b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/tbdex/wallet/PfiAllowListTest.kt
new file mode 100644
index 000000000..14d35b0a1
--- /dev/null
+++ b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/tbdex/wallet/PfiAllowListTest.kt
@@ -0,0 +1,39 @@
+
+package website.tbd.developer.site.docs.tbdex.wallet
+
+import foundation.identity.did.Service
+import web5.sdk.crypto.InMemoryKeyManager
+import web5.sdk.dids.methods.dht.CreateDidDhtOptions
+import web5.sdk.dids.methods.dht.DidDht
+import java.net.URI
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Assertions.*
+
+/**
+ * Tests for Wallet AllowList guide
+ */
+class PfiAllowListTest {
+
+ @Test
+ fun `check if DID has PFI service`() {
+ val serviceToAdd = Service.builder()
+ .id(URI("pfi"))
+ .type("PFI")
+ .serviceEndpoint("tbdex-pfi.tbddev.org")
+ .build()
+
+ val options = CreateDidDhtOptions(
+ publish = false,
+ services = listOf(serviceToAdd),
+ )
+
+ val pfiDid = DidDht.create(InMemoryKeyManager(), options)
+
+ // :snippet-start: isPFIKt
+ val isPFI = pfiDid.didDocument?.services?.any { it.type == "PFI" } ?: false
+ // :snippet-end:
+
+ assertTrue(isPFI, "DID should have a PFI service")
+ }
+}
diff --git a/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/FanClubVcTest.kt b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/FanClubVcTest.kt
new file mode 100644
index 000000000..6394a461b
--- /dev/null
+++ b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/FanClubVcTest.kt
@@ -0,0 +1,198 @@
+package website.tbd.developer.site.docs.web5.build.verifiablecredentials;
+
+import java.io.ByteArrayOutputStream
+import java.io.PrintStream
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Assertions.*
+
+import web5.sdk.credentials.VerifiableCredential
+import web5.sdk.credentials.PresentationExchange
+import web5.sdk.credentials.model.*
+import web5.sdk.crypto.InMemoryKeyManager
+import web5.sdk.dids.methods.key.DidKey
+import web5.sdk.credentials.VerifiablePresentation
+
+/**
+ * Tests backing the VC Fan Club Workflow
+ */
+internal class FanClubVcTest {
+ data class SwiftiesFanClub(
+ val level: String,
+ val legit: Boolean
+ )
+
+ val fanClubIssuerDid = DidKey.create(InMemoryKeyManager())
+ val aliceDid = DidKey.create(InMemoryKeyManager())
+
+ val vc = VerifiableCredential.create(
+ type = "SwiftiesFanClub",
+ issuer = fanClubIssuerDid.uri,
+ subject = aliceDid.uri,
+ data = SwiftiesFanClub(level = "Stan", legit = true)
+ )
+
+ val signedVcJwt = vc.sign(fanClubIssuerDid)
+
+ val presentationDefinition = PresentationDefinitionV2(
+ id = "presDefId123",
+ name = "Swifties Fan Club Presentation Definition",
+ purpose = "for proving membership in the fan club",
+ inputDescriptors = listOf(
+ InputDescriptorV2(
+ id = "legitness",
+ purpose = "are you legit or not?",
+ constraints = ConstraintsV2(
+ fields = listOf(
+ FieldV2(
+ path = listOf("$.vc.credentialSubject.legit")
+ )
+ )
+ )
+ )
+ )
+ )
+
+ @Test
+ fun `createDidsKt imports dids package and creates dids`() {
+ // :snippet-start: createDidsKt
+ val fanClubIssuerDid = DidKey.create(InMemoryKeyManager())
+ val aliceDid = DidKey.create(InMemoryKeyManager())
+ // :snippet-end:
+ assertTrue(fanClubIssuerDid.uri.startsWith("did:key:"), "fanClubIssuerDid should start with 'did:key:'")
+ assertTrue(aliceDid.uri.startsWith("did:key:"), "aliceDid should start with 'did:key:'")
+ }
+
+@Test
+ fun `createClassCredentialKt imports credentials package and creates class for credential`() {
+ val createClassCredentialKt =
+ """
+ // :snippet-start: createClassCredentialKt
+ import web5.sdk.credentials.VerifiableCredential
+ import web5.sdk.credentials.PresentationExchange
+
+ data class SwiftiesFanClub(
+ // indicates the fan's dedication level
+ val level: String,
+
+ // indicates if the fan is a genuine Swiftie
+ val legit: Boolean
+ )
+ // :snippet-end:
+ """
+ }
+
+ @Test
+ fun `createFanClubVcKt creates a vc for fan club`() {
+ // :snippet-start: createFanClubVcKt
+ val vc = VerifiableCredential.create(
+ type = "SwiftiesFanClub",
+ issuer = fanClubIssuerDid.uri,
+ subject = aliceDid.uri,
+ data = SwiftiesFanClub(level = "Stan", legit = true)
+ )
+ // :snippet-end:
+
+ assertEquals("SwiftiesFanClub", vc.type)
+ assertEquals("Stan", vc.vcDataModel.credentialSubject.claims["level"])
+ assertEquals(true, vc.vcDataModel.credentialSubject.claims["legit"])
+ }
+
+ @Test
+ fun `signFanClubVcKt signs a vc for fan club and returns jwt`() {
+ // :snippet-start: signFanClubVcKt
+ val signedVcJwt = vc.sign(fanClubIssuerDid)
+ // :snippet-end:
+
+ assertTrue(signedVcJwt is String, "signedVcJwt should be a String")
+ }
+
+ @Test
+ fun `createAndValidatePresentationKt creates and validates presentation definitionl`() {
+ // :snippet-start: createAndValidatePresentationKt
+ val presentationDefinition = PresentationDefinitionV2(
+ id = "presDefId123",
+ name = "Swifties Fan Club Presentation Definition",
+ purpose = "for proving membership in the fan club",
+ inputDescriptors = listOf(
+ InputDescriptorV2(
+ id = "legitness",
+ purpose = "are you legit or not?",
+ constraints = ConstraintsV2(
+ fields = listOf(
+ FieldV2(
+ path = listOf("$.vc.credentialSubject.legit")
+ )
+ )
+ )
+ )
+ )
+ )
+
+ val definitionValidation = PresentationExchange.validateDefinition(presentationDefinition)
+ // :snippet-end:
+ assertDoesNotThrow {PresentationExchange.validateDefinition(presentationDefinition)}
+ }
+
+ @Test
+ fun `satisfiesPresentationDefinitionFanClubVcKt checks if VC satisfies the presentation definition`() {
+ val outContent = ByteArrayOutputStream()
+ System.setOut(PrintStream(outContent))
+
+ // :snippet-start: satisfiesPresentationDefinitionFanClubVcKt
+ // Does VC Satisfy the Presentation Definition
+ try {
+ PresentationExchange.satisfiesPresentationDefinition(listOf(signedVcJwt), presentationDefinition)
+ println("VC Satisfies Presentation Definition!")
+ } catch (err: Exception) {
+ println("VC does not satisfy Presentation Definition: " + err.message)
+ }
+ // :snippet-end:
+
+ assertTrue(outContent.toString().contains("VC Satisfies Presentation Definition!"))
+ System.setOut(System.out)
+ }
+
+ @Test
+ fun `createPresentationFromCredentialsFanClubVcKt creates presentation from credentials and checks the presentation result`() {
+ // :snippet-start: createPresentationFromCredentialsFanClubVcKt
+ // Create Presentation Result that contains a Verifiable Presentation and Presentation Submission
+ val presentationResult = PresentationExchange.createPresentationFromCredentials(listOf(signedVcJwt), presentationDefinition)
+
+ val vp = VerifiablePresentation.create(
+ vcJwts = listOf(signedVcJwt),
+ holder = aliceDid.uri,
+ additionalData = mapOf("presentation_submission" to presentationResult)
+ )
+ println("Presentation Result and Verifiable Presentation" + vp)
+ // :snippet-end:
+ assertEquals("presDefId123", presentationResult.definitionId)
+ assertEquals("VerifiablePresentation", vp.vpDataModel.type)
+ }
+
+ @Test
+ fun `verifyFanClubVcKt checks if VC verification is successful`() {
+ val outContent = ByteArrayOutputStream()
+ System.setOut(PrintStream(outContent))
+
+ // :snippet-start: verifyFanClubVcKt
+ try {
+ VerifiableCredential.verify(signedVcJwt)
+ println("VC Verification successful!")
+ } catch (err: Exception) {
+ println("VC Verification failed:" + err.message)
+ }
+ // :snippet-end:
+ assertTrue(outContent.toString().contains("VC Verification successful!"))
+ System.setOut(System.out)
+ }
+
+ @Test
+ fun `parseFanClubJwtKt parses the signed VC JWT`() {
+ // :snippet-start: parseFanClubJwtKt
+ val parsedVc = VerifiableCredential.parseJwt(signedVcJwt)
+ // :snippet-end:
+ assertEquals("Stan", parsedVc.vcDataModel.credentialSubject.claims["level"])
+ assertEquals(true, parsedVc.vcDataModel.credentialSubject.claims["legit"])
+ }
+}
diff --git a/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/PresentationExchangeTest.kt b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/PresentationExchangeTest.kt
new file mode 100644
index 000000000..f75804024
--- /dev/null
+++ b/site/testsuites/testsuite-kotlin/src/test/kotlin/docs/web5/build/verifiablecredentials/PresentationExchangeTest.kt
@@ -0,0 +1,168 @@
+package website.tbd.developer.site.docs.web5.build.decentralizedidentifiers;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Assertions.*
+import web5.sdk.crypto.InMemoryKeyManager
+import web5.sdk.credentials.PresentationExchange
+import web5.sdk.credentials.VerifiableCredential
+import web5.sdk.credentials.VerifiablePresentation
+import web5.sdk.credentials.model.*
+
+/**
+ * Tests backing the Presentation Exchange Guide
+ */
+internal class PresentationExchangeTest {
+ val signedEmploymentVcJwt = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ"
+ val signedNameAndDobVcJwt = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg"
+ val allCredentials = listOf(signedEmploymentVcJwt, signedNameAndDobVcJwt)
+
+ val presentationDefinition = PresentationDefinitionV2(
+ id = "presDefIdloanAppVerification123",
+ name = "Loan Application Employment Verification",
+ purpose = "To verify applicant’s employment, date of birth, and name",
+ inputDescriptors = listOf(
+ // Employment Verification
+ InputDescriptorV2(
+ id = "employmentVerification",
+ purpose = "Confirm current employment status",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.employmentStatus")))
+ )
+ ),
+ // Date of Birth Verification
+ InputDescriptorV2(
+ id = "dobVerification",
+ purpose = "Confirm the applicant’s date of birth",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.dateOfBirth")))
+ )
+ ),
+ // Name Verification
+ InputDescriptorV2(
+ id = "nameVerification",
+ purpose = "Confirm the applicant’s legal name",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.name")))
+ )
+ )
+ )
+)
+
+ @Test
+ fun `getLoanAppPresentationDefinitionKt `() {
+ // :snippet-start: getLoanAppPresentationDefinitionKt
+ val presentationDefinition = PresentationDefinitionV2(
+ id = "presDefIdloanAppVerification123",
+ name = "Loan Application Employment Verification",
+ purpose = "To verify applicant’s employment, date of birth, and name",
+ inputDescriptors = listOf(
+ // Employment Verification
+ InputDescriptorV2(
+ id = "employmentVerification",
+ purpose = "Confirm current employment status",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.employmentStatus")))
+ )
+ ),
+ // Date of Birth Verification
+ InputDescriptorV2(
+ id = "dobVerification",
+ purpose = "Confirm the applicant’s date of birth",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.dateOfBirth")))
+ )
+ ),
+ // Name Verification
+ InputDescriptorV2(
+ id = "nameVerification",
+ purpose = "Confirm the applicant’s legal name",
+ constraints = ConstraintsV2(
+ fields = listOf(FieldV2(path = listOf("$.vc.credentialSubject.name")))
+ )
+ )
+ )
+ )
+ // :snippet-end:
+
+ assertEquals("presDefIdloanAppVerification123", presentationDefinition.id)
+ assertEquals("Loan Application Employment Verification", presentationDefinition.name)
+ assertEquals("To verify applicant’s employment, date of birth, and name", presentationDefinition.purpose)
+ assertEquals(3, presentationDefinition.inputDescriptors.size)
+ }
+
+ @Test
+ fun `selectCredentialsForPexKt selects VCs that match presentation defintion`() {
+ // :snippet-start: selectCredentialsForPexKt
+ val selectedCredentials = PresentationExchange.selectCredentials(
+ vcJwts = allCredentials,
+ presentationDefinition = presentationDefinition
+ )
+ // :snippet-end:
+ assertNotNull(selectedCredentials, "Selected credentials should not be null")
+ assertTrue(selectedCredentials is List<*>, "Selected credentials should be a list")
+ assertEquals(2, selectedCredentials.size, "Selected credentials should contain 2 items")
+ assertTrue(signedEmploymentVcJwt in selectedCredentials, "Selected credentials should contain the employment VC JWT")
+ assertTrue(signedNameAndDobVcJwt in selectedCredentials, "Selected credentials should contain the name and DOB VC JWT")
+ }
+
+ @Test
+ fun `satisfiesPresentationDefinitionForPexKt checks if VCs satisfy PD`() {
+ val selectedCredentials = allCredentials
+ assertDoesNotThrow {
+ // :snippet-start: satisfiesPresentationDefinitionForPexKt
+ try {
+ PresentationExchange.satisfiesPresentationDefinition(
+ vcJwts = selectedCredentials,
+ presentationDefinition = presentationDefinition
+ )
+ } catch (e: Exception) {
+ // Handle errors here
+ }
+ // :snippet-end:
+ }
+ }
+
+ @Test
+ fun `createPresentationFromCredentialsForPexKt creates a presentation result`() {
+ val holderDid = "did:key:zQ3shXrAnbgfytQYQjifUm2EcBBbRAeAeGfgC4TZrjw4X71iZ"
+ val selectedCredentials = allCredentials
+ // :snippet-start: createPresentationFromCredentialsForPexKt
+ val presentationResult = PresentationExchange.createPresentationFromCredentials(
+ vcJwts = selectedCredentials,
+ presentationDefinition = presentationDefinition
+ )
+
+ val verifiablePresentation = VerifiablePresentation.create(
+ vcJwts = selectedCredentials,
+ holder = holderDid,
+ additionalData = mapOf("presentation_submission" to presentationResult)
+ )
+ // :snippet-end:
+ assertNotNull(verifiablePresentation, "Verifiable Presentation should not be null")
+ assertEquals(holderDid, verifiablePresentation.holder, "Holder DID should match")
+ }
+
+ @Test
+ fun `validVerifiablePresentationForPexKt creates a valid VP`() {
+ val holderDid = "did:key:zQ3shXrAnbgfytQYQjifUm2EcBBbRAeAeGfgC4TZrjw4X71iZ"
+ val selectedCredentials = allCredentials
+ val presentationResult = PresentationExchange.createPresentationFromCredentials(
+ vcJwts = selectedCredentials,
+ presentationDefinition = presentationDefinition
+ )
+ val verifiablePresentation = VerifiablePresentation.create(
+ vcJwts = selectedCredentials,
+ holder = holderDid,
+ additionalData = mapOf("presentation_submission" to presentationResult)
+ )
+ // :snippet-start: validVerifiablePresentationForPexKt
+ val verifiablePresentationDataModelMap = verifiablePresentation.vpDataModel.toMap()
+ val mappedPresentationSubmission = verifiablePresentationDataModelMap["presentation_submission"] as? PresentationSubmission
+ // :snippet-end:
+
+ assertNotNull(mappedPresentationSubmission, "Mapped Presentation Submission should not be null")
+ assertEquals(presentationResult.definitionId, mappedPresentationSubmission?.definitionId, "Definition ID should match")
+ }
+}
+
+