diff --git a/client/src/App.tsx b/client/src/App.tsx index fa51591a..2e0ecf6b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -42,7 +42,7 @@ function App() { if (connectionDate && lastServerReset) { if (connectionDate < lastServerReset) { navigate('/') - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } } }, [connectionDate, lastServerReset]) diff --git a/client/src/api/ConnectionApi.ts b/client/src/api/ConnectionApi.ts index 7cadc87e..35623a77 100644 --- a/client/src/api/ConnectionApi.ts +++ b/client/src/api/ConnectionApi.ts @@ -2,7 +2,15 @@ import type { AxiosResponse } from 'axios' import { apiCall } from './BaseUrl' -export const createInvitation = (agentName?: string, agentImageUrl?: string): Promise => { +export const createOobInvitation = (agentName?: string, agentImageUrl?: string): Promise => { + return apiCall.post('/oob/create-invitation', { + autoAcceptConnection: true, + label: agentName, + imageUrl: agentImageUrl, + }) +} + +export const createLegacyInvitation = (agentName?: string, agentImageUrl?: string): Promise => { return apiCall.post('/oob/create-legacy-invitation', { autoAcceptConnection: true, label: agentName, diff --git a/client/src/api/CredentialApi.ts b/client/src/api/CredentialApi.ts index 77051c69..b1e41c70 100644 --- a/client/src/api/CredentialApi.ts +++ b/client/src/api/CredentialApi.ts @@ -3,9 +3,13 @@ import type { AxiosResponse } from 'axios' import { apiCall } from './BaseUrl' -export const issueCredential = async (connectionId: string, data: CredentialData): Promise => { +export const issueCredential = async ( + connectionId: string, + data: CredentialData, + protocolVersion: 'v1' | 'v2' +): Promise => { return apiCall.post(`/credentials/offer-credential`, { - protocolVersion: 'v1', + protocolVersion: protocolVersion, connectionId: connectionId, credentialFormats: { indy: { diff --git a/client/src/pages/dashboard/DashboardPage.tsx b/client/src/pages/dashboard/DashboardPage.tsx index 7b1a2913..ead677fb 100644 --- a/client/src/pages/dashboard/DashboardPage.tsx +++ b/client/src/pages/dashboard/DashboardPage.tsx @@ -62,12 +62,12 @@ export const DashboardPage: React.FC = () => { const ERROR_DESCRIPTION = `That's not gone well. Please restart the demo.` const routeError = () => { navigate('/demo') - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } const completeDemo = () => { navigate('/') - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) if (currentCharacter) trackEvent('demo-character-completed', { diff --git a/client/src/pages/dashboard/components/ProfileCard.tsx b/client/src/pages/dashboard/components/ProfileCard.tsx index b065f529..34a83367 100644 --- a/client/src/pages/dashboard/components/ProfileCard.tsx +++ b/client/src/pages/dashboard/components/ProfileCard.tsx @@ -22,7 +22,7 @@ export const ProfileCard: React.FC = ({ currentCharacter }) => { before you switch to another character.` const reset = () => { - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } const cancel = () => { diff --git a/client/src/pages/landing/components/MainSection.tsx b/client/src/pages/landing/components/MainSection.tsx index 4bb9a8cb..27b5ba75 100644 --- a/client/src/pages/landing/components/MainSection.tsx +++ b/client/src/pages/landing/components/MainSection.tsx @@ -83,6 +83,7 @@ export const MainSection: React.FC = () => {
= ({ dispatch(fetchAllUseCasesByCharId(currentCharacter.id)) } else { // something went wrong so reset - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } } @@ -235,7 +235,7 @@ export const OnboardingContainer: React.FC = ({ const leave = () => { navigate('/') - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } return ( diff --git a/client/src/pages/onboarding/OnboardingPage.tsx b/client/src/pages/onboarding/OnboardingPage.tsx index 8bad192c..96816d00 100644 --- a/client/src/pages/onboarding/OnboardingPage.tsx +++ b/client/src/pages/onboarding/OnboardingPage.tsx @@ -41,7 +41,7 @@ export const OnboardingPage: React.FC = () => { dispatch(fetchAllUseCasesByCharId(currentCharacter.id)) navigate('/dashboard') } else { - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) dispatch(fetchWallets()) dispatch(fetchAllCharacters()) setMounted(true) diff --git a/client/src/pages/onboarding/steps/AcceptCredential.tsx b/client/src/pages/onboarding/steps/AcceptCredential.tsx index 563c4d21..19a03096 100644 --- a/client/src/pages/onboarding/steps/AcceptCredential.tsx +++ b/client/src/pages/onboarding/steps/AcceptCredential.tsx @@ -49,10 +49,11 @@ export const AcceptCredential: React.FC = ({ content, connectionId, crede (x) => x.state === 'credential-issued' || x.state === 'done' ) + let { protocolVersion } = useCredentials() useEffect(() => { if (credentials.length === 0) { currentCharacter.starterCredentials.forEach((item) => { - dispatch(issueCredential({ connectionId: connectionId, cred: item })) + dispatch(issueCredential({ connectionId: connectionId, cred: item, protocolVersion })) trackEvent('credential-issued') }) setCredentialsIssued(true) @@ -94,9 +95,11 @@ export const AcceptCredential: React.FC = ({ content, connectionId, crede const routeError = () => { navigate('/demo') - dispatch({ type: 'demo/RESET' }) + dispatch({ type: 'demo/resetState' }) } + protocolVersion = useCredentials().protocolVersion + const sendNewCredentials = () => { credentials.forEach((cred) => { if (cred.state !== 'credential-issued' && cred.state !== 'done') { @@ -110,7 +113,8 @@ export const AcceptCredential: React.FC = ({ content, connectionId, crede ) }) - if (newCredential) dispatch(issueCredential({ connectionId: connectionId, cred: newCredential })) + if (newCredential) + dispatch(issueCredential({ connectionId: connectionId, cred: newCredential, protocolVersion })) } }) closeFailedRequestModal() diff --git a/client/src/pages/onboarding/steps/SetupConnection.tsx b/client/src/pages/onboarding/steps/SetupConnection.tsx index 902df430..ce02193e 100644 --- a/client/src/pages/onboarding/steps/SetupConnection.tsx +++ b/client/src/pages/onboarding/steps/SetupConnection.tsx @@ -10,6 +10,7 @@ import { Loader } from '../../../components/Loader' import { QRCode } from '../../../components/QRCode' import { useAppDispatch } from '../../../hooks/hooks' import { useInterval } from '../../../hooks/useInterval' +import { useConnection } from '../../../slices/connection/connectionSelectors' import { createInvitation, fetchConnectionById, @@ -36,9 +37,10 @@ export const SetupConnection: React.FC = ({ }) => { const dispatch = useAppDispatch() const isCompleted = connectionState === 'response-sent' || connectionState === 'completed' + const { useLegacyInvitations } = useConnection() useEffect(() => { - if (!isCompleted) dispatch(createInvitation()) + if (!isCompleted) dispatch(createInvitation({ useLegacyInvitations })) }, []) useEffect(() => { diff --git a/client/src/pages/useCase/steps/StepConnection.tsx b/client/src/pages/useCase/steps/StepConnection.tsx index 8e32dc63..c930d982 100644 --- a/client/src/pages/useCase/steps/StepConnection.tsx +++ b/client/src/pages/useCase/steps/StepConnection.tsx @@ -10,6 +10,7 @@ import { fade, fadeX } from '../../../FramerAnimations' import { QRCode } from '../../../components/QRCode' import { useAppDispatch } from '../../../hooks/hooks' import { useInterval } from '../../../hooks/useInterval' +import { useConnection } from '../../../slices/connection/connectionSelectors' import { createInvitation, fetchConnectionById, @@ -27,9 +28,12 @@ export const StepConnection: React.FC = ({ step, connection, entity }) => const dispatch = useAppDispatch() const { id, state, invitationUrl, outOfBandId } = connection const isCompleted = state === 'response-sent' || state === 'completed' + const { useLegacyInvitations } = useConnection() useEffect(() => { - if (!isCompleted) dispatch(createInvitation(entity)) + if (!isCompleted) { + dispatch(createInvitation({ entity, useLegacyInvitations })) + } }, []) useInterval( diff --git a/client/src/pages/useCase/steps/StepCredential.tsx b/client/src/pages/useCase/steps/StepCredential.tsx index c282ca33..4e86320b 100644 --- a/client/src/pages/useCase/steps/StepCredential.tsx +++ b/client/src/pages/useCase/steps/StepCredential.tsx @@ -11,6 +11,7 @@ import { ActionCTA } from '../../../components/ActionCTA' import { Loader } from '../../../components/Loader' import { useAppDispatch } from '../../../hooks/hooks' import { useInterval } from '../../../hooks/useInterval' +import { useCredentials } from '../../../slices/credentials/credentialsSelectors' import { deleteCredentialById, fetchCredentialsByConId, @@ -42,6 +43,8 @@ export const StepCredential: React.FC = ({ step, connectionId, issueCrede ) const [issuedCredData, setIssuedCredData] = useState([]) + let { protocolVersion } = useCredentials() + const issueCreds = () => { // get attributes from proof let attributes: Attribute[] = [] @@ -60,7 +63,7 @@ export const StepCredential: React.FC = ({ step, connectionId, issueCrede // issue credentials credentialData.forEach((item) => { - dispatch(issueCredential({ connectionId: connectionId, cred: item })) + dispatch(issueCredential({ connectionId: connectionId, cred: item, protocolVersion })) trackEvent('credential-issued') }) } @@ -76,6 +79,8 @@ export const StepCredential: React.FC = ({ step, connectionId, issueCrede !credentialsAccepted ? 1000 : null ) + protocolVersion = useCredentials().protocolVersion + const sendNewCredentials = () => { credentials.forEach((cred) => { if (cred.state !== 'credential-issued' && cred.state !== 'done') { @@ -88,7 +93,8 @@ export const StepCredential: React.FC = ({ step, connectionId, issueCrede credClass.metadata.get('_internal/indyCredential')?.credentialDefinitionId ) }) - if (newCredential) dispatch(issueCredential({ connectionId: connectionId, cred: newCredential })) + if (newCredential) + dispatch(issueCredential({ connectionId: connectionId, cred: newCredential, protocolVersion })) } }) closeFailedRequestModal() diff --git a/client/src/slices/characters/charactersSlice.ts b/client/src/slices/characters/charactersSlice.ts index 37b71ae7..79fbf23b 100644 --- a/client/src/slices/characters/charactersSlice.ts +++ b/client/src/slices/characters/charactersSlice.ts @@ -44,6 +44,9 @@ const characterSlice = createSlice({ state.isLoading = false state.currentCharacter = action.payload }) + .addCase('demo/resetState', () => { + return initialState + }) }, }) diff --git a/client/src/slices/connection/connectionSlice.ts b/client/src/slices/connection/connectionSlice.ts index ad2a9749..77bdb493 100644 --- a/client/src/slices/connection/connectionSlice.ts +++ b/client/src/slices/connection/connectionSlice.ts @@ -8,6 +8,7 @@ export interface ConnectionState { invitationUrl?: string outOfBandId?: string isLoading: boolean + useLegacyInvitations: boolean } const initialState: ConnectionState = { @@ -16,6 +17,7 @@ const initialState: ConnectionState = { invitationUrl: undefined, outOfBandId: undefined, isLoading: false, + useLegacyInvitations: true, } const connectionSlice = createSlice({ @@ -28,6 +30,9 @@ const connectionSlice = createSlice({ state.invitationUrl = undefined state.isLoading = false }, + setUseLegacyInvitations: (state, action) => { + state.useLegacyInvitations = action.payload + }, }, extraReducers: (builder) => { builder @@ -62,9 +67,15 @@ const connectionSlice = createSlice({ state.invitationUrl = undefined state.isLoading = false }) + .addCase('demo/resetState', (state) => { + return { + ...initialState, + useLegacyInvitations: state.useLegacyInvitations, + } + }) }, }) -export const { clearConnection } = connectionSlice.actions +export const { clearConnection, setUseLegacyInvitations } = connectionSlice.actions export default connectionSlice.reducer diff --git a/client/src/slices/connection/connectionThunks.ts b/client/src/slices/connection/connectionThunks.ts index 7e23fe77..24e52361 100644 --- a/client/src/slices/connection/connectionThunks.ts +++ b/client/src/slices/connection/connectionThunks.ts @@ -1,4 +1,4 @@ -import type { Entity } from '../../slices/types' +import type { CreateInvitationProps } from '../../slices/types' import { createAsyncThunk } from '@reduxjs/toolkit' @@ -14,7 +14,15 @@ export const fetchConnectionByOutOfBandId = createAsyncThunk('connection/fetchBy return response.data }) -export const createInvitation = createAsyncThunk('connection/createInvitation', async (entity?: Entity) => { - const response = await Api.createInvitation(entity?.name, entity?.imageUrl) - return response.data -}) +export const createInvitation = createAsyncThunk( + 'connection/createInvitation', + async (createInvitationOptions?: CreateInvitationProps) => { + const entity = createInvitationOptions?.entity + + const response = createInvitationOptions?.useLegacyInvitations + ? await Api.createLegacyInvitation(entity?.name, entity?.imageUrl) + : await Api.createOobInvitation(entity?.name, entity?.imageUrl) + + return response.data + } +) diff --git a/client/src/slices/credentials/credentialsSlice.ts b/client/src/slices/credentials/credentialsSlice.ts index 62234e36..c852e03e 100644 --- a/client/src/slices/credentials/credentialsSlice.ts +++ b/client/src/slices/credentials/credentialsSlice.ts @@ -15,6 +15,7 @@ interface CredentialState { issuedCredentials: CredentialExchangeRecord[] isLoading: boolean isIssueCredentialLoading: boolean + protocolVersion: 'v1' | 'v2' error: SerializedError | undefined } @@ -23,6 +24,7 @@ const initialState: CredentialState = { issuedCredentials: [], isLoading: true, isIssueCredentialLoading: true, + protocolVersion: 'v1', error: undefined, } @@ -36,6 +38,9 @@ const credentialSlice = createSlice({ ) state.credentials = [] }, + setProtocolVersion: (state, action) => { + state.protocolVersion = action.payload + }, }, extraReducers: (builder) => { builder @@ -87,9 +92,15 @@ const credentialSlice = createSlice({ state.credentials = [] state.isLoading = false }) + .addCase('demo/resetState', (state) => { + return { + ...initialState, + protocolVersion: state.protocolVersion, + } + }) }, }) -export const { clearCredentials } = credentialSlice.actions +export const { clearCredentials, setProtocolVersion } = credentialSlice.actions export default credentialSlice.reducer diff --git a/client/src/slices/credentials/credentialsThunks.ts b/client/src/slices/credentials/credentialsThunks.ts index 7035b06b..70c0ff07 100644 --- a/client/src/slices/credentials/credentialsThunks.ts +++ b/client/src/slices/credentials/credentialsThunks.ts @@ -11,8 +11,8 @@ export const fetchCredentialsByConId = createAsyncThunk('credentials/fetchAllByC export const issueCredential = createAsyncThunk( 'credentials/issueCredential', - async (data: { connectionId: string; cred: CredentialData }) => { - const response = await Api.issueCredential(data.connectionId, data.cred) + async (data: { connectionId: string; cred: CredentialData; protocolVersion: 'v1' | 'v2' }) => { + const response = await Api.issueCredential(data.connectionId, data.cred, data.protocolVersion) return response.data } ) diff --git a/client/src/slices/onboarding/onboardingSlice.ts b/client/src/slices/onboarding/onboardingSlice.ts index 80cdcd3c..89c23bf6 100644 --- a/client/src/slices/onboarding/onboardingSlice.ts +++ b/client/src/slices/onboarding/onboardingSlice.ts @@ -34,6 +34,11 @@ const onboardingSlice = createSlice({ state.isCompleted = false }, }, + extraReducers: (builder) => { + builder.addCase('demo/resetState', () => { + return initialState + }) + }, }) export const { diff --git a/client/src/slices/preferences/preferencesSlice.ts b/client/src/slices/preferences/preferencesSlice.ts index db0995a5..d300d629 100644 --- a/client/src/slices/preferences/preferencesSlice.ts +++ b/client/src/slices/preferences/preferencesSlice.ts @@ -50,7 +50,7 @@ const preferencesSlice = createSlice({ }, extraReducers: (builder) => { builder - .addCase('demo/RESET', (state) => { + .addCase('demo/resetState', (state) => { state.darkMode = localStorage.getItem('theme') === 'dark' state.connectionDate = undefined }) diff --git a/client/src/slices/proof/proofSlice.ts b/client/src/slices/proof/proofSlice.ts index b30af9ae..8f5f863b 100644 --- a/client/src/slices/proof/proofSlice.ts +++ b/client/src/slices/proof/proofSlice.ts @@ -54,6 +54,9 @@ const proofSlice = createSlice({ state.proof = undefined state.isLoading = false }) + .addCase('demo/resetState', () => { + return initialState + }) }, }) diff --git a/client/src/slices/section/sectionSlice.ts b/client/src/slices/section/sectionSlice.ts index c6b1c13f..0e99073f 100644 --- a/client/src/slices/section/sectionSlice.ts +++ b/client/src/slices/section/sectionSlice.ts @@ -23,9 +23,13 @@ const sectionSlice = createSlice({ }, }, extraReducers: (builder) => { - builder.addCase('clearUseCase', (state) => { - state.section = undefined - }) + builder + .addCase('clearUseCase', (state) => { + state.section = undefined + }) + .addCase('demo/resetState', () => { + return initialState + }) }, }) diff --git a/client/src/slices/types.ts b/client/src/slices/types.ts index 52b37a4b..eb216566 100644 --- a/client/src/slices/types.ts +++ b/client/src/slices/types.ts @@ -100,6 +100,11 @@ export interface Entity { imageUrl?: string } +export interface CreateInvitationProps { + entity?: Entity + useLegacyInvitations?: boolean +} + export interface Colors { primary: string secondary: string diff --git a/client/src/slices/useCases/useCasesSlice.ts b/client/src/slices/useCases/useCasesSlice.ts index 4ae4be1a..c55dbca9 100644 --- a/client/src/slices/useCases/useCasesSlice.ts +++ b/client/src/slices/useCases/useCasesSlice.ts @@ -62,6 +62,9 @@ const useCaseSlice = createSlice({ state.sectionCount = 0 state.stepCount = 0 }) + .addCase('demo/resetState', () => { + return initialState + }) }, }) diff --git a/client/src/slices/wallets/walletsSlice.ts b/client/src/slices/wallets/walletsSlice.ts index 8501fbcd..815488d9 100644 --- a/client/src/slices/wallets/walletsSlice.ts +++ b/client/src/slices/wallets/walletsSlice.ts @@ -27,6 +27,9 @@ const characterSlice = createSlice({ state.isLoading = false state.wallets = action.payload }) + .addCase('demo/resetState', () => { + return initialState + }) }, }) diff --git a/client/src/utils/KBar.tsx b/client/src/utils/KBar.tsx index 2617d402..137a992c 100644 --- a/client/src/utils/KBar.tsx +++ b/client/src/utils/KBar.tsx @@ -6,6 +6,8 @@ import Confetti from 'react-confetti' import { confettiFade } from '../FramerAnimations' import { useAppDispatch } from '../hooks/hooks' import { fetchAllCharacters } from '../slices/characters/charactersThunks' +import { setUseLegacyInvitations } from '../slices/connection/connectionSlice' +import { setProtocolVersion } from '../slices/credentials/credentialsSlice' import { usePreferences } from '../slices/preferences/preferencesSelectors' import { resetDashboard, setDarkMode } from '../slices/preferences/preferencesSlice' import { fetchWallets } from '../slices/wallets/walletsThunks' @@ -54,7 +56,7 @@ export const KBar: React.FunctionComponent = ({ children }) = }, { id: 'resetDemo', - name: 'Reset demo', + name: 'Reset demo (including configuration options)', shortcut: ['r'], keywords: 'Reset demo', perform: () => { @@ -63,9 +65,81 @@ export const KBar: React.FunctionComponent = ({ children }) = dispatch(fetchAllCharacters()) }, }, + { + id: 'resetState', + name: 'Reset demo', + shortcut: ['s'], + keywords: 'Reset state', + perform: () => { + dispatch({ type: 'demo/resetState' }) + dispatch(fetchWallets()) + dispatch(fetchAllCharacters()) + }, + }, + { + id: 'configuration', + name: 'Change demo configuration...', + keywords: 'configuration', + section: 'Configuration', + }, + { + id: 'issue-credential-protocol-version', + name: 'Select credential protocol version', + keywords: 'issue credential protocol version', + section: '', + parent: 'configuration', + }, + { + id: 'issue-credential-protocol-version-1', + name: 'V1', + keywords: 'issue credential protocol version 1', + section: '', + perform: () => { + dispatch(setProtocolVersion('v1')) + }, + parent: 'issue-credential-protocol-version', + }, + { + id: 'issue-credential-protocol-version-2', + name: 'V2', + keywords: 'issue credential protocol version 2', + section: '', + perform: () => { + dispatch(setProtocolVersion('v2')) + }, + parent: 'issue-credential-protocol-version', + }, + { + id: 'invitation-type', + name: 'Change connection invitation type', + keywords: 'invitation type', + section: '', + parent: 'configuration', + }, + { + id: 'invitation-type-oob', + name: 'Out Of Band', + keywords: 'invitation type oob', + section: '', + perform: () => { + dispatch(setUseLegacyInvitations(false)) + }, + parent: 'invitation-type', + }, + { + id: 'invitation-type-legacy', + name: 'Legacy (RFC 0160)', + keywords: 'invitation type legacy', + section: '', + perform: () => { + dispatch(setUseLegacyInvitations(true)) + }, + parent: 'invitation-type', + }, { id: 'theme', name: 'Change themeā€¦', + shortcut: ['t'], keywords: 'interface color dark light', section: 'Preferences', }, diff --git a/client/src/utils/RenderResults.tsx b/client/src/utils/RenderResults.tsx index 37d865c3..53d58415 100644 --- a/client/src/utils/RenderResults.tsx +++ b/client/src/utils/RenderResults.tsx @@ -28,6 +28,7 @@ const ResultItem = forwardRef( return (
{ + it('successfully completes school use case', () => { + cy.visit('/') + cy.get('[data-cy=try-demo-button]').click() + + cy.get('[data-cy=next-onboarding-step]').click() + cy.get('[data-cy=use-wallet]').first().click() + cy.get('[data-cy=small-button]').click() + + cy.get('[data-cy=select-char]').first().click() + cy.intercept('POST', `${API_URL}/oob/create-legacy-invitation`).as('createInvitation') + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find( + (credentialRecord) => credentialRecord.threadId === threadId && credentialRecord.state === 'offer-received' + ) + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((credentialRecord) => credentialRecord.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy="next-onboarding-step"]').click() + }) + }) + + cy.get('[data-cy=next-onboarding-step]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + + cy.get('[data-cy=select-use-case]').first().click() + + cy.get('[data-cy=start-container]') + cy.get('[data-cy=small-button]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', `${API_URL}/proofs/request-proof`).as('createProof') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@createProof').then((interception) => { + const threadId = interception.response?.body.threadId + cy.request('GET', `${TEST_AGENT_URL}/proofs/`).should((response) => { + const record = response.body.find((x) => x.threadId === threadId && x.state === 'request-received') + cy.request('POST', `${TEST_AGENT_URL}/proofs/${record.id}/accept-request`).then(() => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find((x) => x.threadId === threadId && x.state === 'offer-received') + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((x) => x.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + + cy.get('[data-cy=end-container]') + cy.get('[data-cy=standard-button]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + }) +}) diff --git a/cypress/e2e/issue_credential_version_2_use_case.cy.ts b/cypress/e2e/issue_credential_version_2_use_case.cy.ts new file mode 100644 index 00000000..e9547c38 --- /dev/null +++ b/cypress/e2e/issue_credential_version_2_use_case.cy.ts @@ -0,0 +1,141 @@ +const API_URL = Cypress.env('apiUrl') +const TEST_AGENT_URL = 'http://localhost:9000' + +describe('Onboarding demo test using issue credential protocol version 2', () => { + it('successfully completes school use case', () => { + cy.visit('/') + cy.get('[data-cy=try-demo-button]').click() + + const shortcut = Cypress.platform === 'darwin' ? 'command+k' : 'ctrl+k' + cy.get('body').type(`{${shortcut}}`) + cy.get('[data-cy=configuration]') + .click() + .get('[data-cy=issue-credential-protocol-version]') + .click() + .get('[data-cy=issue-credential-protocol-version-2]') + .click() + + cy.get('[data-cy=next-onboarding-step]').click() + cy.get('[data-cy=use-wallet]').first().click() + cy.get('[data-cy=small-button]').click() + + cy.get('[data-cy=select-char]').first().click() + cy.intercept('POST', `${API_URL}/oob/create-legacy-invitation`).as('createInvitation') + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find( + (credentialRecord) => credentialRecord.threadId === threadId && credentialRecord.state === 'offer-received' + ) + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((credentialRecord) => credentialRecord.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy="next-onboarding-step"]').click() + }) + }) + + cy.get('[data-cy=next-onboarding-step]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + + cy.get('[data-cy=select-use-case]').first().click() + + cy.get('[data-cy=start-container]') + cy.get('[data-cy=small-button]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', `${API_URL}/proofs/request-proof`).as('createProof') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@createProof').then((interception) => { + const threadId = interception.response?.body.threadId + cy.request('GET', `${TEST_AGENT_URL}/proofs/`).should((response) => { + const record = response.body.find((x) => x.threadId === threadId && x.state === 'request-received') + cy.request('POST', `${TEST_AGENT_URL}/proofs/${record.id}/accept-request`).then(() => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find((x) => x.threadId === threadId && x.state === 'offer-received') + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((x) => x.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + + cy.get('[data-cy=end-container]') + cy.get('[data-cy=standard-button]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + }) +}) diff --git a/cypress/e2e/usecase_page.cy.ts b/cypress/e2e/legacy_invitation_usecase_page.cy.ts similarity index 94% rename from cypress/e2e/usecase_page.cy.ts rename to cypress/e2e/legacy_invitation_usecase_page.cy.ts index b9082595..8d057270 100644 --- a/cypress/e2e/usecase_page.cy.ts +++ b/cypress/e2e/legacy_invitation_usecase_page.cy.ts @@ -1,7 +1,7 @@ const API_URL = Cypress.env('apiUrl') const TEST_AGENT_URL = 'http://localhost:9000' -describe('UseCase Page', () => { +describe('Onboarding demo test using legacy invitation', () => { it('successfully completes school use case', () => { cy.visit('/demo') cy.get('[data-cy=next-onboarding-step]').click() @@ -43,7 +43,7 @@ describe('UseCase Page', () => { cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(3000) // wait for the test agent request to be processed + cy.wait(5000) // wait for the test agent request to be processed cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { const cred = resp.body.find((credentialRecord) => credentialRecord.threadId === threadId) cy.wrap(cred).its('state').should('equal', 'done') @@ -58,7 +58,6 @@ describe('UseCase Page', () => { cy.get('[data-cy=select-use-case]').first().click() - cy.intercept('POST', `${API_URL}/connections/create-invitation`).as('createInvitation') cy.get('[data-cy=start-container]') cy.get('[data-cy=small-button]').click() @@ -88,7 +87,7 @@ describe('UseCase Page', () => { const record = response.body.find((x) => x.threadId === threadId && x.state === 'request-received') cy.request('POST', `${TEST_AGENT_URL}/proofs/${record.id}/accept-request`).then(() => { // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(3000) // wait for the test agent request to be processed + cy.wait(5000) // wait for the test agent request to be processed cy.get('[data-cy=section') cy.get('[data-cy="small-button"]').click() }) @@ -113,7 +112,7 @@ describe('UseCase Page', () => { cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(3000) // wait for the test agent request to be processed + cy.wait(5000) // wait for the test agent request to be processed cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { const cred = resp.body.find((x) => x.threadId === threadId) cy.wrap(cred).its('state').should('equal', 'done') diff --git a/cypress/e2e/oob_invitation_usecase.cy.ts b/cypress/e2e/oob_invitation_usecase.cy.ts new file mode 100644 index 00000000..db973be8 --- /dev/null +++ b/cypress/e2e/oob_invitation_usecase.cy.ts @@ -0,0 +1,141 @@ +const API_URL = Cypress.env('apiUrl') +const TEST_AGENT_URL = 'http://localhost:9000' + +describe('Onboarding demo test using out of band invitation', () => { + it('successfully completes school use case', () => { + cy.visit('/') + cy.get('[data-cy=try-demo-button]').click() + + const shortcut = Cypress.platform === 'darwin' ? 'command+k' : 'ctrl+k' + cy.get('body').type(`{${shortcut}}`) + cy.get('[data-cy=configuration]') + .click() + .get('[data-cy=invitation-type]') + .click() + .get('[data-cy=invitation-type-oob]') + .click() + + cy.get('[data-cy=next-onboarding-step]').click() + cy.get('[data-cy=use-wallet]').first().click() + cy.get('[data-cy=small-button]').click() + + cy.get('[data-cy=select-char]').first().click() + cy.intercept('POST', `${API_URL}/oob/create-invitation`).as('createInvitation') + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=next-onboarding-step]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find( + (credentialRecord) => credentialRecord.threadId === threadId && credentialRecord.state === 'offer-received' + ) + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((credentialRecord) => credentialRecord.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy="next-onboarding-step"]').click() + }) + }) + + cy.get('[data-cy=next-onboarding-step]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + + cy.get('[data-cy=select-use-case]').first().click() + + cy.get('[data-cy=start-container]') + cy.get('[data-cy=small-button]').click() + + cy.wait('@createInvitation').then((interception) => { + const body = { invitationUrl: interception.response?.body.invitationUrl, autoAcceptConnection: true } + + const oobId = interception.response?.body.outOfBandRecord.id + + cy.intercept('GET', `${API_URL}/connections?outOfBandId=${oobId}`).as('getConnectionRecord') + + cy.request('POST', `${TEST_AGENT_URL}/oob/receive-invitation-url`, body) + + cy.wait(['@getConnectionRecord']).then((inter) => { + const record = inter.response?.body[0] + cy.wrap(record).its('state').should('not.equal', 'invited') + }) + }) + + cy.intercept('POST', `${API_URL}/proofs/request-proof`).as('createProof') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@createProof').then((interception) => { + const threadId = interception.response?.body.threadId + cy.request('GET', `${TEST_AGENT_URL}/proofs/`).should((response) => { + const record = response.body.find((x) => x.threadId === threadId && x.state === 'request-received') + cy.request('POST', `${TEST_AGENT_URL}/proofs/${record.id}/accept-request`).then(() => { + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.intercept('POST', '/credentials/offer-credential').as('offerCredential') + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + + cy.wait('@offerCredential').then((interception) => { + const connectionId = interception.response?.body.connectionId + const threadId = interception.response?.body.threadId + + cy.request('GET', `${TEST_AGENT_URL}/credentials/`).should((response) => { + const testAgentRecord = response.body.find((x) => x.threadId === threadId && x.state === 'offer-received') + + cy.request('POST', `${TEST_AGENT_URL}/credentials/${testAgentRecord.id}/accept-offer`) + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(5000) // wait for the test agent request to be processed + cy.request('GET', `${API_URL}/demo/credentials/${connectionId}`).should((resp) => { + const cred = resp.body.find((x) => x.threadId === threadId) + cy.wrap(cred).its('state').should('equal', 'done') + }) + + cy.get('[data-cy=section') + cy.get('[data-cy="small-button"]').click() + }) + }) + + cy.get('[data-cy=end-container]') + cy.get('[data-cy=standard-button]').click() + cy.url().should('be.equal', `${Cypress.config('baseUrl')}/dashboard`) + }) +})