Skip to content
Open
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
60 changes: 38 additions & 22 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
name: Playwright Tests

on:
push:
branches: [ main, master ]
branches: [ main ]
pull_request:
branches: [ main, master ]

jobs:
test:
timeout-minutes: 60
e2e-tests:
runs-on: ubuntu-latest

env:
CI: true
NEXTAUTH_URL: "http://localhost:3000"
NEXTAUTH_SECRET: "testsecret123"
GOOGLE_CLIENT_ID: "test"
GOOGLE_CLIENT_SECRET: "test"
# Firebase: required for next build (collecting page data). Add repo secrets to override.
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY || 'AIzaSyDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMY1' }}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || 'demo-project.firebaseapp.com' }}
NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID || 'demo-project' }}
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || 'demo-project.appspot.com' }}
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || '123456789012' }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID || '1:123456789012:web:abcdef1234567890abcdef' }}
# Resend: required for next build (verification/send route). Add RESEND_API_KEY secret to override.
NEXT_PUBLIC_RESEND_API_KEY: ${{ secrets.NEXT_PUBLIC_RESEND_API_KEY || secrets.RESEND_API_KEY || 're_dummy_for_playwright' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm install -g yarn && yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Build application
run: yarn build
- name: Run Playwright tests
run: yarn playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- uses: actions/checkout@v3

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: yarn install

- name: Build the project
run: yarn build

- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Run tests
run: npx playwright test
4 changes: 3 additions & 1 deletion app/credentialForm/form/types/Types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISkill } from 'hr-context'
// Interfaces for the credential data
export interface Address {
addressCountry: string
Expand Down Expand Up @@ -60,7 +61,8 @@ export interface FormData {
explainAnswer: string
howKnow: string
qualification: string
[key: string]: string | number | Portfolio[] | undefined
skills: ISkill[]
[key: string]: string | number | Portfolio[] | ISkill[] | undefined
}

// Component Props for the form
Expand Down
36 changes: 36 additions & 0 deletions app/utils/normalization/hrContextSkillClaim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ISkill } from 'hr-context'
import { FormData } from '../../credentialForm/form/types/Types'
export type SkillClaimFormData = {
personName: string
personId?: string
skills: ISkill[]
evidence?: Array<{ id: string; name: string; type?: string; description?: string }>
}

export function normalizeSkillClaimFormData(formData: FormData): SkillClaimFormData {
const skill = formData.skills?.[0] // pick the first skill in the array
const skillName = skill?.name ?? formData.credentialName ?? ''
const skillDescription = formData.credentialDescription ?? undefined
const narrative = typeof formData.description === 'string'
? formData.description
: typeof formData.credentialDescription === 'string'
? formData.credentialDescription
: ''

const skills = [
{
name: skillName,
description: skillDescription,
durationPerformed: formData.credentialDuration ?? '',
narrative,
image: formData.evidenceLink ? { id: formData.evidenceLink, type: 'Image' } : undefined
}
] as ISkill[]

const evidence = formData.portfolio.length ? formData.portfolio.map((p: any) => ({ id: p.url, name: p.name, description: p.description })) : []
return {
personName: formData.fullName ?? '',
skills,
evidence: evidence.length ? evidence : []
}
}
13 changes: 6 additions & 7 deletions app/utils/signCred.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CredentialEngine, GoogleDriveStorage } from '@cooperation/vc-storage'
import { FormData } from '../credentialForm/form/types/Types'
import { normalizeSkillClaimFormData, SkillClaimFormData } from './normalization/hrContextSkillClaim'
import { ISkillClaimCredential } from 'hr-context'

interface FormDataI {
expirationDate: string
Expand Down Expand Up @@ -92,13 +94,10 @@ const signCred = async (
})
} else {
formData = generateCredentialData(data)
console.log('🚀 ~ formData:', formData)
signedVC = await credentialEngine.signVC({
data: formData,
type: 'VC',
keyPair,
issuerId: issuerDid
})
console.log('🚀 ~ signCred ~ formData:', formData)
const normalizedData: SkillClaimFormData = normalizeSkillClaimFormData(formData as unknown as FormData)
console.log('🚀 ~ signCred ~ normalizedData:', normalizedData)
signedVC = await credentialEngine.signSkillClaimVC(normalizedData as unknown as ISkillClaimCredential, keyPair, issuerDid)
}

return signedVC
Expand Down
67 changes: 67 additions & 0 deletions app/utils/signSkillClaim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
CredentialEngine,
GoogleDriveStorage,
saveToGoogleDrive
} from '@cooperation/vc-storage'
import type { ISkillClaimCredential } from 'hr-context'
import type { FormData } from '../credentialForm/form/types/Types'
import { normalizeSkillClaimFormData, SkillClaimFormData } from './normalization/hrContextSkillClaim'

/**
* Sign a SkillClaimCredential using the HR Context data model.
* Uses the shared storage and engine. Creates a DID if keyPair/issuerId are not provided.
*
* @param storage - Shared GoogleDriveStorage instance (e.g. from useGoogleDrive)
* @param engine - Shared CredentialEngine instance (e.g. from getCredentialEngine)
* @param input - Form data + skills (or pre-built SkillClaimFormData)
* @param options - Optional keyPair and issuerId (if already have DID); saveToDrive to persist
* @returns The signed SkillClaimCredential
*/
export async function signSkillClaim(
storage: GoogleDriveStorage,
engine: CredentialEngine,
formData: FormData,
options?: {
keyPair?: any
issuerId?: string
saveToDrive?: boolean
}
): Promise<ISkillClaimCredential | { signedVC: ISkillClaimCredential; file: any }> {
if (!storage || !engine)
throw new Error('Storage and CredentialEngine are required.')

const normalizedData: SkillClaimFormData = normalizeSkillClaimFormData(formData)

let keyPair = options?.keyPair
let issuerId = options?.issuerId

if (!keyPair || !issuerId) {
const { didDocument, keyPair: kp } = await engine.createDID()
keyPair = kp
issuerId = didDocument.id
normalizedData.personId = normalizedData.personId ?? issuerId
}

if (!issuerId) throw new Error('Issuer DID is required.')

try {
const signedVC = await engine.signSkillClaimVC(
normalizedData as unknown as ISkillClaimCredential,
keyPair,
issuerId
)
if (options?.saveToDrive) {
const file = await saveToGoogleDrive({
storage,
data: signedVC,
type: 'VC'
})
return { signedVC, file }
}

return signedVC
} catch (error) {
console.error('🚀 ~ signSkillClaim ~ error:', JSON.stringify(error, null, 2))
throw error
}
}
Loading