Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iam, app): implements poh stamp provider #75

Merged
merged 10 commits into from
May 18, 2022
Merged
2 changes: 0 additions & 2 deletions .github/workflows/cd-iam-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jobs:
- name: Lerna Bootstrap
run: lerna bootstrap
- name: Run Tests
env:
RPC_URL: ${{ secrets.GOERLI_RPC_URL }}
run: |
yarn test:iam
yarn test:identity
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,5 @@ jobs:
run: lerna bootstrap
- name: Run Tests
run: yarn test
env:
RPC_URL: ${{ secrets.GOERLI_RPC_URL }}
- name: Run Linter
run: yarn lint
2 changes: 0 additions & 2 deletions .github/workflows/manual-iam-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ jobs:
- name: Lerna Bootstrap
run: lerna bootstrap
- name: Run Tests
env:
RPC_URL: ${{ secrets.GOERLI_RPC_URL }}
run: |
yarn test:iam
yarn test:identity
Expand Down
5 changes: 5 additions & 0 deletions app/__test-fixtures__/databaseStorageFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const ensStampFixture: Stamp = {
credential,
};

export const pohStampFixture: Stamp = {
provider: "Poh",
credential,
};

export const twitterStampFixture: Stamp = {
provider: "Twitter",
credential,
Expand Down
13 changes: 13 additions & 0 deletions app/__test-fixtures__/verifiableCredentialResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,16 @@ export const SUCCESFUL_ENS_RESULT: VerifiableCredentialRecord = {
challenge: credential,
credential: credential,
};

export const SUCCESFUL_POH_RESULT: VerifiableCredentialRecord = {
record: {
type: "Poh",
address: "0xcF323CE817E25b4F784bC1e14c9A99A525fDC50f",
version: "0.0.0",
poh: "Is registered",
},
signature:
"0xbdbac10fdb0921e73df7575e47cbda484be550c36570bc146bed90c5dcb7435e64178cb263864f48af1ad6eeee1ee94c9a0794a3812ae861f8898a973233abea1c",
challenge: credential,
credential: credential,
};
199 changes: 199 additions & 0 deletions app/__tests__/components/ProviderCards/PohCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React from "react";
import { render, screen, fireEvent, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { PohCard } from "../../../components/ProviderCards";

import { UserContext, UserContextState } from "../../../context/userContext";
import { mockAddress, mockWallet } from "../../../__test-fixtures__/onboardHookValues";
import { STAMP_PROVIDERS } from "../../../config/providers";
import { pohStampFixture } from "../../../__test-fixtures__/databaseStorageFixtures";
import { SUCCESFUL_POH_RESULT } from "../../../__test-fixtures__/verifiableCredentialResults";
import { fetchVerifiableCredential } from "@dpopp/identity/dist/commonjs";

jest.mock("@dpopp/identity/dist/commonjs", () => ({
fetchVerifiableCredential: jest.fn(),
}));
jest.mock("../../../utils/onboard.ts");

const mockHandleConnection = jest.fn();
const mockCreatePassport = jest.fn();
const handleAddStamp = jest.fn();
const mockUserContext: UserContextState = {
loggedIn: true,
passport: undefined,
isLoadingPassport: false,
allProvidersState: {
Poh: {
providerSpec: STAMP_PROVIDERS.Poh,
stamp: undefined,
},
},
handleAddStamp: handleAddStamp,
handleCreatePassport: mockCreatePassport,
handleConnection: mockHandleConnection,
address: mockAddress,
wallet: mockWallet,
signer: undefined,
walletLabel: mockWallet.label,
};

describe("when user has not verfied with PohProvider", () => {
it("should display a verification button", () => {
render(
<UserContext.Provider value={mockUserContext}>
<PohCard />
</UserContext.Provider>
);

const verifyButton = screen.queryByRole("button", {
name: /Verify/,
});

expect(verifyButton).toBeInTheDocument();
});
});

describe("when user has verified with PohProvider", () => {
it("should display is verified", () => {
render(
<UserContext.Provider
value={{
...mockUserContext,
allProvidersState: {
Poh: {
providerSpec: STAMP_PROVIDERS.Poh,
stamp: pohStampFixture,
},
},
}}
>
<PohCard />
</UserContext.Provider>
);

const verified = screen.queryByText(/Verified/);

expect(verified).toBeInTheDocument();
});
});

describe("when the verify button is clicked", () => {
afterEach(() => {
jest.clearAllMocks();
});

describe("and when a successful POH result is returned", () => {
beforeEach(() => {
(fetchVerifiableCredential as jest.Mock).mockResolvedValue(SUCCESFUL_POH_RESULT);
});

it("the modal displays the verify button", async () => {
render(
<UserContext.Provider value={mockUserContext}>
<PohCard />
</UserContext.Provider>
);

const initialVerifyButton = screen.queryByRole("button", {
name: /Verify/,
});

fireEvent.click(initialVerifyButton!);

const verifyModal = await screen.findByRole("dialog");
const verifyModalButton = screen.getByTestId("modal-verify");

expect(verifyModal).toBeInTheDocument();

await waitFor(() => {
expect(verifyModalButton).toBeInTheDocument();
});
});

it("clicking verify adds the stamp", async () => {
render(
<UserContext.Provider value={mockUserContext}>
<PohCard />
</UserContext.Provider>
);

const initialVerifyButton = screen.queryByRole("button", {
name: /Verify/,
});

// Click verify button on ens card
fireEvent.click(initialVerifyButton!);

// Wait to see the verify button on the modal
await waitFor(() => {
const verifyModalButton = screen.getByTestId("modal-verify");
expect(verifyModalButton).toBeInTheDocument();
});

const finalVerifyButton = screen.queryByRole("button", {
name: /Verify/,
});

// Click the verify button on modal
fireEvent.click(finalVerifyButton!);

expect(handleAddStamp).toBeCalled();
});

it("clicking cancel closes the modal and a stamp should not be added", async () => {
(fetchVerifiableCredential as jest.Mock).mockResolvedValue(SUCCESFUL_POH_RESULT);
render(
<UserContext.Provider value={mockUserContext}>
<PohCard />
</UserContext.Provider>
);

const initialVerifyButton = screen.queryByRole("button", {
name: /Verify/,
});

fireEvent.click(initialVerifyButton!);

// Wait to see the cancel button on the modal
let modalCancelButton: HTMLElement | null = null;
await waitFor(() => {
modalCancelButton = screen.queryByRole("button", {
name: /Cancel/,
});
expect(modalCancelButton).toBeInTheDocument();
});

fireEvent.click(modalCancelButton!);

expect(handleAddStamp).not.toBeCalled();

await waitForElementToBeRemoved(modalCancelButton);
expect(modalCancelButton).not.toBeInTheDocument();
});
});

describe("and when a failed POH result is returned", () => {
it("modal displays a failed message", async () => {
(fetchVerifiableCredential as jest.Mock).mockRejectedValue("ERROR");
render(
<UserContext.Provider value={mockUserContext}>
<PohCard />
</UserContext.Provider>
);

const initialVerifyButton = screen.queryByRole("button", {
name: /Verify/,
});

fireEvent.click(initialVerifyButton!);

const verifyModal = await screen.findByRole("dialog");
const verifyModalText = screen.getByText("The Proof of Humanity Status for this address Is not Registered");

expect(verifyModal).toBeInTheDocument();

await waitFor(() => {
expect(verifyModalText).toBeInTheDocument();
});
});
});
});
4 changes: 4 additions & 0 deletions app/__tests__/pages/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const mockUserContext: UserContextState = {
providerSpec: STAMP_PROVIDERS.Ens,
stamp: undefined,
},
Poh: {
providerSpec: STAMP_PROVIDERS.Poh,
stamp: undefined,
},
Twitter: {
providerSpec: STAMP_PROVIDERS.Twitter,
stamp: undefined,
Expand Down
3 changes: 2 additions & 1 deletion app/components/CardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React from "react";

// --- Identity Providers
import { GoogleCard, SimpleCard, EnsCard, TwitterCard } from "./ProviderCards";
import { GoogleCard, SimpleCard, EnsCard, PohCard, TwitterCard } from "./ProviderCards";

export const CardList = (): JSX.Element => {
return (
Expand All @@ -13,6 +13,7 @@ export const CardList = (): JSX.Element => {
<GoogleCard />
<EnsCard />
<TwitterCard />
<PohCard />
</div>
</div>
</section>
Expand Down
93 changes: 93 additions & 0 deletions app/components/ProviderCards/PohCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// --- React Methods
import React, { useContext, useState } from "react";

// --- Identity tools
import { fetchVerifiableCredential } from "@dpopp/identity/dist/commonjs";

// pull context
import { UserContext } from "../../context/userContext";

import { PROVIDER_ID, Stamp } from "@dpopp/types";

const iamUrl = process.env.NEXT_PUBLIC_DPOPP_IAM_URL || "";

// --- import components
import { Card } from "../Card";
import { VerifyModal } from "../VerifyModal";
import { useDisclosure } from "@chakra-ui/react";

const providerId: PROVIDER_ID = "Poh";

export default function PohCard(): JSX.Element {
const { address, signer, handleAddStamp, allProvidersState } = useContext(UserContext);
const { isOpen, onOpen, onClose } = useDisclosure();
const [credentialResponse, SetCredentialResponse] = useState<Stamp | undefined>(undefined);
const [credentialResponseIsLoading, setCredentialResponseIsLoading] = useState(false);
const [pohVerified, SetPohVerified] = useState<Stamp | undefined>(undefined);

const handleFetchCredential = (): void => {
setCredentialResponseIsLoading(true);
fetchVerifiableCredential(
iamUrl,
{
type: "Poh",
version: "0.0.0",
address: address || "",
proofs: {
valid: address ? "true" : "false",
},
},
signer as { signMessage: (message: string) => Promise<string> }
)
.then((verified: { record: any; credential: any }): void => {
SetPohVerified(verified.record?.poh);
SetCredentialResponse({
provider: "Poh",
credential: verified.credential,
});
})
.catch((e: any): void => {})
.finally((): void => {
setCredentialResponseIsLoading(false);
});
};

const handleUserVerify = (): void => {
if (credentialResponse) {
handleAddStamp(credentialResponse);
}
onClose();
};

const issueCredentialWidget = (
<>
<button
className="verify-btn"
data-testid="button-verify"
onClick={() => {
SetCredentialResponse(undefined);
handleFetchCredential();
onOpen();
}}
>
Verify
</button>
<VerifyModal
isOpen={isOpen}
onClose={onClose}
stamp={credentialResponse}
handleUserVerify={handleUserVerify}
verifyData={<>{`The Proof of Humanity Status for this address ${pohVerified || "Is not Registered"}`}</>}
isLoading={credentialResponseIsLoading}
/>
</>
);

return (
<Card
providerSpec={allProvidersState[providerId].providerSpec}
verifiableCredential={allProvidersState[providerId].stamp?.credential}
issueCredentialWidget={issueCredentialWidget}
/>
);
}
1 change: 1 addition & 0 deletions app/components/ProviderCards/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as GoogleCard } from "./GoogleCard";
export { default as SimpleCard } from "./SimpleCard";
export { default as EnsCard } from "./EnsCard";
export { default as PohCard } from "./PohCard";
export { default as TwitterCard } from "./TwitterCard";
4 changes: 4 additions & 0 deletions app/config/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const STAMP_PROVIDERS: Readonly<Providers> = {
name: "Ens",
description: "Ens name",
},
Poh: {
name: "POH",
description: "Proof of Humanity",
},
Twitter: {
name: "Twitter",
description: "Twitter name",
Expand Down
Loading