Skip to content

Commit

Permalink
Merge pull request passportxyz#866 from gitcoinco/849-corrupt-stamp-t…
Browse files Browse the repository at this point in the history
…ests

849 corrupt stamp tests
  • Loading branch information
lucianHymer authored Jan 25, 2023
2 parents 8c7ac83 + 9b3a786 commit 0f831f4
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 263 deletions.
3 changes: 2 additions & 1 deletion app/__test-fixtures__/contextTestHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,14 @@ export const makeTestCeramicContext = (initialState?: Partial<CeramicContextStat
stamp: undefined,
},
},
ceramicErrors: { error: false },
passportLoadResponse: undefined,
handleAddStamp: jest.fn(),
handleAddStamps: jest.fn(),
handleCreatePassport: jest.fn(),
handleDeleteStamp: jest.fn(),
handleDeleteStamps: jest.fn(),
handleCheckRefreshPassport: () => Promise.resolve(true),
passportHasCacaoError: () => false,
...initialState,
};
};
Expand Down
15 changes: 3 additions & 12 deletions app/__tests__/components/RefreshModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ describe("RefreshStampModal", () => {
mockUserContext,
{
...mockCeramicContext,
ceramicErrors: {
error: true,
stamps: ["streamid"],
},
passportHasCacaoError: () => true,
},
<RefreshStampModal isOpen={true} onClose={jest.fn()} />
);
Expand All @@ -48,10 +45,7 @@ describe("RefreshStampModal", () => {
mockUserContext,
{
...mockCeramicContext,
ceramicErrors: {
error: true,
stamps: ["streamid"],
},
passportHasCacaoError: () => true,
},
<RefreshStampModal isOpen={true} onClose={jest.fn()} />
);
Expand All @@ -65,10 +59,7 @@ describe("RefreshStampModal", () => {
mockUserContext,
{
...mockCeramicContext,
ceramicErrors: {
error: true,
stamps: ["streamid"],
},
passportHasCacaoError: () => true,
},
<RefreshStampModal isOpen={true} onClose={jest.fn()} />
);
Expand Down
12 changes: 3 additions & 9 deletions app/__tests__/pages/Dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,7 @@ describe("when a user clicks on the Passport logo", () => {
mockUserContext,
{
...mockCeramicContext,
ceramicErrors: {
error: true,
stamps: ["streamid"],
},
passportHasCacaoError: () => true,
},
<Router>
<Dashboard />
Expand All @@ -246,17 +243,14 @@ describe("when a user clicks on the Passport logo", () => {
mockUserContext,
{
...mockCeramicContext,
ceramicErrors: {
error: true,
stamps: ["streamid"],
},
passportHasCacaoError: () => true,
},
<Router>
<Dashboard />
</Router>
);

await fireEvent.click(screen.getByText("Reset Passport"));
fireEvent.click(screen.getByText("Reset Passport"));
await waitFor(() => {
expect(screen.getByText("Refresh Modal")).toBeInTheDocument();
});
Expand Down
8 changes: 5 additions & 3 deletions app/components/PlatformCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const PlatformCard = ({
getUpdatedPlatforms,
}: PlatformCardProps): JSX.Element => {
// import all providers
const { allProvidersState, ceramicErrors, handleDeleteStamps } = useContext(CeramicContext);
const { allProvidersState, passportHasCacaoError, handleDeleteStamps } = useContext(CeramicContext);

// useDisclosure to control JSON modal
const {
Expand All @@ -57,6 +57,8 @@ export const PlatformCard = ({
onClose: onCloseRemoveStampModal,
} = useDisclosure();

const disabled = passportHasCacaoError();

// returns a single Platform card
return (
<div className="w-1/2 p-2 md:w-1/2 xl:w-1/4" key={`${platform.name}${i}`}>
Expand Down Expand Up @@ -93,7 +95,7 @@ export const PlatformCard = ({
{selectedProviders[platform.platform].length > 0 ? (
<>
<Menu>
<MenuButton disabled={ceramicErrors?.error} className="verify-btn flex" data-testid="card-menu-button">
<MenuButton disabled={disabled} className="verify-btn flex" data-testid="card-menu-button">
<div className="m-auto flex justify-center">
<svg
className="m-1 mr-2"
Expand Down Expand Up @@ -179,7 +181,7 @@ export const PlatformCard = ({
) : (
<button
className="verify-btn"
disabled={ceramicErrors?.error}
disabled={disabled}
ref={btnRef.current}
onClick={(e) => {
if (platform.enablePlatformCardUpdate) {
Expand Down
111 changes: 49 additions & 62 deletions app/context/ceramicContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import { Passport, PassportWithErrors, PassportError, PLATFORM_ID, PROVIDER_ID, Stamp } from "@gitcoin/passport-types";
import {
Passport,
PassportLoadResponse,
PassportLoadStatus,
PLATFORM_ID,
PROVIDER_ID,
Stamp,
} from "@gitcoin/passport-types";
import { ProviderSpec, STAMP_PROVIDERS } from "../config/providers";
import { CeramicDatabase } from "@gitcoin/passport-database-client";
import { useViewerConnection } from "@self.id/framework";
Expand Down Expand Up @@ -45,7 +52,8 @@ export interface CeramicContextState {
handleDeleteStamps: (providerIds: PROVIDER_ID[]) => Promise<void>;
handleCheckRefreshPassport: () => Promise<boolean>;
userDid: string | undefined;
ceramicErrors: PassportError | undefined;
passportHasCacaoError: () => boolean;
passportLoadResponse?: PassportLoadResponse;
}

export const platforms = new Map<PLATFORM_ID, PlatformProps>();
Expand Down Expand Up @@ -441,8 +449,9 @@ const startingState: CeramicContextState = {
handleDeleteStamp: async (streamId: string) => {},
handleDeleteStamps: async () => {},
handleCheckRefreshPassport: async () => false,
passportHasCacaoError: () => false,
userDid: undefined,
ceramicErrors: undefined,
passportLoadResponse: undefined,
};

export const CeramicContext = createContext(startingState);
Expand All @@ -453,7 +462,7 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
const [isLoadingPassport, setIsLoadingPassport] = useState<IsLoadingPassportState>(IsLoadingPassportState.Loading);
const [passport, setPassport] = useState<Passport | undefined>(undefined);
const [userDid, setUserDid] = useState<string | undefined>();
const [ceramicErrors, setCeramicErrors] = useState<PassportError | undefined>();
const [passportLoadResponse, setPassportLoadResponse] = useState<PassportLoadResponse | undefined>();
const [viewerConnection] = useViewerConnection();

const { address } = useContext(UserContext);
Expand All @@ -464,7 +473,7 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
setCeramicDatabase(undefined);
setPassport(undefined);
setUserDid(undefined);
setCeramicErrors(undefined);
setPassportLoadResponse(undefined);
};
}, [address]);

Expand Down Expand Up @@ -502,63 +511,34 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
}, [ceramicDatabase]);

const fetchPassport = async (database: CeramicDatabase, skipLoadingState?: boolean): Promise<void> => {
let passportHasError = false;
let failedStamps: string[] = [];

if (!skipLoadingState) setIsLoadingPassport(IsLoadingPassportState.Loading);
// fetch, clean and set the new Passport state
const passportResponse = (await database.getPassport()) as PassportWithErrors | undefined | false;

// Ceramic error being thrown relies on the false response from getPassport. This is a temporary fix
let passport;
if (passportResponse === undefined || passportResponse === false) {
passport = passportResponse;
} else {
passport = passportResponse.passport;
}

if (passportResponse && passportResponse?.errors?.passport) {
const passportCacaoError = await database.checkPassportCACAOError();
if (passportCacaoError) {
datadogRum.addError("Passport CACAO error -- error thrown on initial fetch within getPassport", { address });
passportHasError = true;
}
}
if (passportResponse && passportResponse?.errors?.stamps) failedStamps = passportResponse.errors.stamps;

if (passport) {
passport = cleanPassport(passport, database) as Passport;
hydrateAllProvidersState(passport);
setPassport(passport);
if (!skipLoadingState) setIsLoadingPassport(IsLoadingPassportState.Idle);
} else if (passportResponse === false) {
const passportCacaoError = await database.checkPassportCACAOError();
if (passportCacaoError) {
datadogRum.addError(
"Passport CACAO error -- undefined or null return while fetching getPassport from ceramic",
{ address }
);
passportHasError = true;
} else {
// fetch, clean and set the new Passport state
const { status, errorDetails, passport } = await database.getPassport();

switch (status) {
case "Success":
case "StampCacaoError":
const cleanedPassport = cleanPassport(passport, database) as Passport;
hydrateAllProvidersState(cleanedPassport);
setPassport(cleanedPassport);
if (!skipLoadingState) setIsLoadingPassport(IsLoadingPassportState.Idle);
break;
case "PassportCacaoError":
datadogRum.addError("Passport CACAO error -- error thrown on initial fetch", { address });
break;
case "DoesNotExist":
handleCreatePassport();
}
} else {
const passportCacaoError = await database.checkPassportCACAOError();
if (passportCacaoError) {
datadogRum.addError("Passport CACAO error -- checking before throwing IsLoadingPassportState.FailedToConnect", {
address,
});
passportHasError = true;
} else {
break;
case "ExceptionRaised":
// something is wrong with Ceramic...
datadogRum.addError("Ceramic connection failed", { address });
setPassport(undefined);
if (!skipLoadingState) setIsLoadingPassport(IsLoadingPassportState.FailedToConnect);
}
break;
}
const error = passportHasError || failedStamps.length > 0;

setCeramicErrors({ passport: passportHasError, stamps: failedStamps, error });
setPassportLoadResponse({ passport, status, errorDetails });
};

const cleanPassport = (
Expand All @@ -585,22 +565,22 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {

const handleCheckRefreshPassport = async (): Promise<boolean> => {
let success = true;
if (ceramicDatabase && ceramicErrors) {
let passportHasError = ceramicErrors.passport;
let failedStamps = ceramicErrors.stamps || [];
if (ceramicDatabase && passportLoadResponse) {
let passportHasError = passportLoadResponse.status === "PassportCacaoError";
let failedStamps = passportLoadResponse.errorDetails?.stampStreamIds || [];
try {
if (ceramicErrors.passport) {
if (passportHasError) {
passportHasError = !(await ceramicDatabase.refreshPassport());
}

if (ceramicErrors.stamps && ceramicErrors.stamps.length) {
if (failedStamps && failedStamps.length) {
try {
await ceramicDatabase.deleteStampIDs(ceramicErrors.stamps);
await ceramicDatabase.deleteStampIDs(failedStamps);
failedStamps = [];
} catch {}
}

// fetchpassport to reset passport state
// fetchPassport to reset passport state
await fetchPassport(ceramicDatabase);

success = !passportHasError && !failedStamps.length;
Expand Down Expand Up @@ -656,7 +636,7 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
if (passport) {
// set stamps into allProvidersState
let newAllProviderState = { ...startingAllProvidersState };
passport.stamps.forEach((stamp: Stamp, index: number) => {
passport.stamps.forEach((stamp: Stamp) => {
const { provider } = stamp;
const providerState = allProvidersState[provider];
if (providerState) {
Expand All @@ -677,6 +657,12 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
setAllProviderState(startingAllProvidersState);
};

const passportHasCacaoError = (): boolean => {
const errorStatuses: PassportLoadStatus[] = ["PassportCacaoError", "StampCacaoError"];
if (passportLoadResponse) return errorStatuses.includes(passportLoadResponse.status);
else return false;
};

const stateMemo = useMemo(
() => ({
passport,
Expand Down Expand Up @@ -705,7 +691,8 @@ export const CeramicContextProvider = ({ children }: { children: any }) => {
handleDeleteStamp,
handleCheckRefreshPassport,
userDid,
ceramicErrors,
passportLoadResponse,
passportHasCacaoError,
};

return <CeramicContext.Provider value={providerProps}>{children}</CeramicContext.Provider>;
Expand Down
4 changes: 2 additions & 2 deletions app/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { getExpiredStamps } from "../utils/helpers";
import { RefreshStampModal } from "../components/RefreshStampModal";

export default function Dashboard() {
const { passport, isLoadingPassport, ceramicErrors } = useContext(CeramicContext);
const { passport, isLoadingPassport, passportHasCacaoError } = useContext(CeramicContext);
const { wallet, toggleConnection, handleDisconnection } = useContext(UserContext);

const { isOpen, onOpen, onClose } = useDisclosure();
Expand Down Expand Up @@ -130,7 +130,7 @@ export default function Dashboard() {
</div>
)}

{ceramicErrors && ceramicErrors.error && (
{passportHasCacaoError() && (
<Banner>
<div className="flex w-full justify-center">
We have detected some broken stamps in your passport. Your passport is currently locked because of this. We
Expand Down
Loading

0 comments on commit 0f831f4

Please sign in to comment.