diff --git a/.github/workflows/build-from-test.yml b/.github/workflows/build-from-test.yml index 583c9831d..a7275446e 100644 --- a/.github/workflows/build-from-test.yml +++ b/.github/workflows/build-from-test.yml @@ -192,7 +192,6 @@ jobs: docker load -i '/tmp/image-${{ matrix.name }}-${{ env.ENVIRONMENT }}.tar' rm -rf '/tmp/image-${{ matrix.name }}-${{ env.ENVIRONMENT }}.tar' docker push ${{ steps.image_lowercase.outputs.lowercase }}-${{ env.CLEAN_NETWORK}}:${{ env.COMMIT_TAG }} - docker push ${{ steps.image_lowercase.outputs.lowercase }}-${{ env.CLEAN_NETWORK}}:${{ env.STATIC_TAG }} - name: Notify Qovery of new image tag run: | @@ -202,7 +201,7 @@ jobs: -d '{ "image_name": "${{ steps.image_lowercase.outputs.lowercase }}-${{ env.CLEAN_NETWORK }}", "tag": "${{ env.COMMIT_TAG }}" - }' + }' - name: Add tag as a PR comment uses: ubie-oss/comment-to-merged-pr-action@v0.3.3 diff --git a/CHANGELOG.md b/CHANGELOG.md index a12869c1b..95c10954a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ changes. - Preserve maintenance ending banner state on the wallet connection change [Issue 3681](https://github.com/IntersectMBO/govtool/issues/3681) - Add authors for Live Voting Governance Actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745) +- Add support for ed25519 author signature validation on gov actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745) ### Fixed diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index a215919f3..5ea162660 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -304,6 +304,7 @@ SELECT COALESCE(cv.ccAbstainVotes, 0) cc_abstain_votes, prev_gov_action.index as prev_gov_action_index, encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash, + off_chain_vote_data.json as json_content, COALESCE( json_agg( json_build_object( @@ -367,4 +368,5 @@ GROUP BY off_chain_vote_gov_action_data.title, off_chain_vote_gov_action_data.abstract, off_chain_vote_gov_action_data.motivation, - off_chain_vote_gov_action_data.rationale; + off_chain_vote_gov_action_data.rationale, + off_chain_vote_data.json; diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index f8424d149..3c14926e0 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -245,6 +245,7 @@ proposalToResponse timeZone Types.Proposal {..} = proposalResponseCcAbstainVotes = proposalCcAbstainVotes, proposalResponsePrevGovActionIndex = proposalPrevGovActionIndex, proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash, + proposalResponseJson = proposalJson, proposalResponseAuthors = ProposalAuthors <$> proposalAuthors } diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index f649cc855..d14fd54cb 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -401,6 +401,7 @@ data ProposalResponse , proposalResponseCcAbstainVotes :: Integer , proposalResponsePrevGovActionIndex :: Maybe Integer , proposalResponsePrevGovActionTxHash :: Maybe HexText + , proposalResponseJson :: Maybe Value , proposalResponseAuthors :: Maybe ProposalAuthors } deriving (Generic, Show) diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 190eb8a78..25c248bd3 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -208,6 +208,7 @@ data Proposal , proposalCcAbstainVotes :: Integer , proposalPrevGovActionIndex :: Maybe Integer , proposalPrevGovActionTxHash :: Maybe Text + , proposalJson :: Maybe Value , proposalAuthors :: Maybe Value } deriving (Show) @@ -242,6 +243,7 @@ instance FromRow Proposal where <*> (floor @Scientific <$> field) -- proposalCcAbstainVotes <*> field -- prevGovActionIndex <*> field -- prevGovActionTxHash + <*> field -- proposalJson <*> field -- proposalAuthors data TransactionStatus = TransactionStatus diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 8426deaa8..0d57e4f8d 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -15,9 +15,10 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.0", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "1.0.3-alfa", + "@intersect.mbo/pdf-ui": "1.0.3-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", + "@noble/ed25519": "^2.3.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/pluginutils": "^5.1.0", "@sentry/react": "^7.77.0", @@ -3424,9 +3425,9 @@ "license": "ISC" }, "node_modules/@intersect.mbo/pdf-ui": { - "version": "1.0.3-alfa", - "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-1.0.3-alfa.tgz", - "integrity": "sha512-jcM1+NDLCMGEQrhge/jNq+uHmBqstbR0XZK68CIi163h0kdd1uCEHjOE4ZdEsMD+uqjdyuTqxGCvuedUQ71FUQ==", + "version": "1.0.3-beta", + "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-1.0.3-beta.tgz", + "integrity": "sha512-zsG3wD3C2k7x3rWPTmrUan22rIuly2ltLMx3lOYN7MIFFnQCeJ1+qXYwnZhvLsgVtzZbAi+gGnDqYggZLgVkoQ==", "dependencies": { "@emurgo/cardano-serialization-lib-asmjs": "^12.0.0-beta.2", "@fontsource/poppins": "^5.0.14", @@ -4445,6 +4446,15 @@ "node": ">=4.0" } }, + "node_modules/@noble/ed25519": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz", + "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index c954bc678..910788d03 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -29,9 +29,10 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.0", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "1.0.3-alfa", + "@intersect.mbo/pdf-ui": "1.0.3-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", + "@noble/ed25519": "^2.3.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/pluginutils": "^5.1.0", "@sentry/react": "^7.77.0", diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx index cd844d375..a7982ef73 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx @@ -1,6 +1,9 @@ -import { useMemo, useState, Fragment } from "react"; +import { useMemo, useState, useEffect } from "react"; import { Box, Tabs, Tab, styled, Skeleton } from "@mui/material"; import { useLocation } from "react-router-dom"; +import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; +import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { CopyButton, ExternalModalButton, Tooltip, Typography } from "@atoms"; import { @@ -23,8 +26,10 @@ import { getFullGovActionId, mapArrayToObjectByKeys, encodeCIP129Identifier, + validateSignature, } from "@utils"; import { MetadataValidationStatus, ProposalData } from "@models"; +import { errorRed, successGreen } from "@/consts"; import { GovernanceActionType } from "@/types/governanceAction"; import { useAppContext } from "@/context"; @@ -77,6 +82,7 @@ export const GovernanceActionDetailsCardData = ({ proposal: { abstract, authors, + json: jsonContent, createdDate, createdEpochNo, details, @@ -369,28 +375,39 @@ export const GovernanceActionDetailsCardData = ({ ( - - + {(authors ?? []).length <= 0 + ? t("govActions.authors.noDataAvailable") + : (authors ?? []).map((author) => ( + + {author.name} - - {idx < arr.length - 1 && } - - )) + + + + + ))} + } textVariant="longText" dataTestId="authors" @@ -498,3 +515,62 @@ const HardforkDetailsTabContent = ({ ); }; + +const AuthorSignatureStatus = ({ + algorithm, + publicKey, + signature, + jsonContent, +}: { + algorithm?: string; + publicKey?: string; + signature?: string; + jsonContent?: Record; +}) => { + const { t } = useTranslation(); + const [isSignatureValid, setIsSignatureValid] = useState( + null, + ); + + useEffect(() => { + let cancelled = false; + async function checkSignature() { + const args = { + jsonContent, + algorithm, + publicKey, + signature, + }; + const result = await validateSignature(args); + if (!cancelled) setIsSignatureValid(result); + } + checkSignature(); + return () => { + cancelled = true; + }; + }, [algorithm, jsonContent, publicKey, signature]); + + if (isSignatureValid === null) { + return ; + } + return ( + + {isSignatureValid ? ( + + ) : ( + + )} + + ); +}; diff --git a/govtool/frontend/src/i18n/locales/en.json b/govtool/frontend/src/i18n/locales/en.json index 439d4ce0b..111029fca 100644 --- a/govtool/frontend/src/i18n/locales/en.json +++ b/govtool/frontend/src/i18n/locales/en.json @@ -423,10 +423,12 @@ "anchorHash": "Metadata anchor hash", "authors": { "noDataAvailable": "No data available", - "title": "Authors", + "title": "Author(s)", "publicKey": "Public Key", "signature": "Signature", - "witnessAlgorithm": "Witness Algorithm" + "witnessAlgorithm": "Witness Algorithm", + "singatureVerified": "Author signature is verified", + "signatureNotVerified": "Author signature is not verified" }, "backToGovActions": "Back to Governance Actions", "castVote": "<0>You voted {{vote}} on this proposal\non {{date}} (Epoch {{epoch}})", diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 0ade19812..8ba3a6a32 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -240,6 +240,7 @@ export type ProposalData = { publicKey?: string; signature?: string; }[]; + json?: Record; } & SubmittedVotesData; export type NewConstitutionAnchor = { diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts index 38c403872..84c563c00 100644 --- a/govtool/frontend/src/utils/index.ts +++ b/govtool/frontend/src/utils/index.ts @@ -37,3 +37,4 @@ export * from "./uniqBy"; export * from "./wait"; export * from "./getBase64ImageDetails"; export * from "./parseBoolean"; +export * from "./validateSignature"; diff --git a/govtool/frontend/src/utils/tests/validateSignature.test.ts b/govtool/frontend/src/utils/tests/validateSignature.test.ts new file mode 100644 index 000000000..fed667560 --- /dev/null +++ b/govtool/frontend/src/utils/tests/validateSignature.test.ts @@ -0,0 +1,170 @@ +/** + * @vitest-environment node + * + * We are switching to the "node" environment because jsdom does not provide a full WebCrypto API, + * specifically window.crypto.subtle, which is required by @noble/ed25519 for signature + * verification. Node.js (v16.8+) includes native crypto.subtle support, making it suitable for + * cryptographic tests. Current browsers support window.crypto.subtle natively, so this issue only + * affects the test environment. */ + +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { validateSignature } from "../validateSignature"; + +describe("validateSignature", () => { + beforeEach(() => { + vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); + }); + + it("returns false if signature is missing", async () => { + const result = await validateSignature({ + ...validInput, + signature: undefined, + }); + expect(result).toBe(false); + }); + + it("returns false if publicKey is missing", async () => { + const result = await validateSignature({ + ...validInput, + publicKey: undefined, + }); + expect(result).toBe(false); + }); + + it("returns false if algorithm is missing", async () => { + const result = await validateSignature({ + ...validInput, + algorithm: undefined, + }); + expect(result).toBe(false); + }); + + it("returns false if message is missing", async () => { + const result = await validateSignature({ + ...validInput, + jsonContent: undefined, + }); + expect(result).toBe(false); + }); + + it("returns false for unsupported algorithm", async () => { + vi.spyOn(console, "error").mockImplementation(() => {}); + const result = await validateSignature({ + ...validInput, + algorithm: "rsa", + }); + expect(result).toBe(false); + expect(console.error).toHaveBeenCalledWith("Unsupported algorithm:", "rsa"); + }); + + describe("Ed25519 algorithm", () => { + it("accepts 'Ed25519' (case-insensitive)", async () => { + const result = await validateSignature({ + ...validInput, + algorithm: "Ed25519", + }); + + expect(result).toBe(true); + }); + + it("accepts 'ed25519' (case-insensitive)", async () => { + const result = await validateSignature({ + ...validInput, + algorithm: "ed25519", + }); + expect(result).toBe(true); + }); + }); +}); + +const validInput = { + jsonContent: { + "@context": { + "@language": "en", + CIP100: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + CIP108: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#", + authors: { + "@container": "@set", + "@context": { + name: "http://xmlns.com/foaf/0.1/name", + witness: { + "@context": { + publicKey: "CIP100:publicKey", + signature: "CIP100:signature", + witnessAlgorithm: "CIP100:witnessAlgorithm", + }, + "@id": "CIP100:witness", + }, + }, + "@id": "CIP100:authors", + }, + body: { + "@context": { + abstract: "CIP108:abstract", + motivation: "CIP108:motivation", + rationale: "CIP108:rationale", + references: { + "@container": "@set", + "@context": { + GovernanceMetadata: "CIP100:GovernanceMetadataReference", + Other: "CIP100:OtherReference", + label: "CIP100:reference-label", + referenceHash: { + "@context": { + hashAlgorithm: "CIP100:hashAlgorithm", + hashDigest: "CIP108:hashDigest", + }, + "@id": "CIP108:referenceHash", + }, + uri: "CIP100:reference-uri", + }, + "@id": "CIP108:references", + }, + title: "CIP108:title", + }, + "@id": "CIP108:body", + }, + hashAlgorithm: "CIP100:hashAlgorithm", + }, + authors: [ + { + name: "Ryan!", + witness: { + publicKey: + "38f01b9b41e7ea4bca5c093e472fa01198ebaf09a55a9e97f7431c3a06df5103", + signature: + "29796c10ccb180c03d6053dffbbce7b6e9028dcfaf76312ebd161ed0ff40001b906b33f460ef99ed0533250a29a05d6aaab04f96aaf7c6d9acb9dfae5f5f1e0f", + witnessAlgorithm: "ed25519", + }, + }, + ], + body: { + abstract: + "This is Ryan's proposal for a Blockchain Ecosystem Budget, which allocates 100 million ada to various projects and initiatives within the ecosystem.", + motivation: "Without funding Ryan cannot buy an island.", + rationale: "uhhhhhhhhhhhhhhhh", + references: [ + { + "@type": "Other", + label: "Lol", + uri: "ipfs://xd", + }, + ], + title: "Ryan Blockchain Ecosystem Budget - 100M ada", + }, + hashAlgorithm: "blake2b-256", + }, + messageHash: + "64e9e4dcbb15d4c4aff3c47119e1c2ce025e45041c3e5c568530d2613c04f9be", + publicKey: "38f01b9b41e7ea4bca5c093e472fa01198ebaf09a55a9e97f7431c3a06df5103", + algorithm: "ed25519", + signature: + "29796c10ccb180c03d6053dffbbce7b6e9028dcfaf76312ebd161ed0ff40001b906b33f460ef99ed0533250a29a05d6aaab04f96aaf7c6d9acb9dfae5f5f1e0f", +}; diff --git a/govtool/frontend/src/utils/validateSignature.ts b/govtool/frontend/src/utils/validateSignature.ts new file mode 100644 index 000000000..1daf5168a --- /dev/null +++ b/govtool/frontend/src/utils/validateSignature.ts @@ -0,0 +1,61 @@ +import { verifyAsync } from "@noble/ed25519"; +import { blake2bHex } from "blakejs"; +import { canonizeJSON } from "./canonizeJSON"; + +export const validateSignature = async ({ + signature, + publicKey, + algorithm, + jsonContent, +}: { + signature?: string; // HEX string representation + publicKey?: string; // HEX string representation + algorithm?: string; + jsonContent?: Record; +}): Promise => { + if (!signature || !publicKey || !algorithm || !jsonContent) { + return false; + } + + try { + const messageHash = await generateHashMessage(jsonContent); + + switch (algorithm) { + case "ed25519": + case "Ed25519": { + return await verifyAsync(signature, messageHash, publicKey); + } + case "CIP-8": + case "CIP-0008": { + throw new Error("CIP-0008 is not supported yet"); + } + default: + console.error("Unsupported algorithm:", algorithm); + return false; + } + } catch (error: unknown) { + const erroMessage = + error && typeof error === "object" && "message" in error + ? (error as { message: string }).message + : String(error); + console.error("Error validating signature:", erroMessage); + return false; + } +}; + +const generateHashMessage = async ( + jsonContent: Record, +): Promise => { + const { body } = jsonContent; + const context = jsonContent["@context"]; + if (!body || !context) { + throw new Error("Missing body or @context in jsonContent"); + } + + const jsonldDoc = { + "@context": context, + body, + }; + + return blake2bHex(await canonizeJSON(jsonldDoc), undefined, 32); +}; diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts index 7b578deb4..946f074cf 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionDetailsPage.ts @@ -17,7 +17,8 @@ export default class BudgetDiscussionDetailsPage { readonly changeVoteYesBtn = this.page.getByTestId( "change-poll-vote-yes-button" ); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyUserLink = this.page.getByTestId("verify-user-link").first(); + readonly verifyDRepLink = this.page.getByTestId("verify-drep-link").first(); readonly readMoreBtn = this.page.getByTestId("read-more-button"); readonly menuButton = this.page.getByTestId("menu-button"); diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts index 76b1187b0..57b2bb2ea 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionPage.ts @@ -14,7 +14,8 @@ export default class BudgetDiscussionPage { readonly proposalBudgetDiscussionBtn = this.page.getByTestId( "propose-a-budget-discussion-button" ); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyUserLink = this.page.getByTestId("verify-user-link").first(); + readonly verifyDRepLink = this.page.getByTestId("verify-drep-link").first(); readonly filterBtn = this.page.getByTestId("filter-button"); readonly sortBtn = this.page.getByTestId("sort-button"); readonly myProposalBtn = this.page.getByTestId( diff --git a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts index 96cc46d98..85d526b64 100644 --- a/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/budgetDiscussionSubmissionPage.ts @@ -48,7 +48,9 @@ export default class BudgetDiscussionSubmissionPage { readonly continueBtn = this.page.getByTestId("continue-button"); readonly addLinkBtn = this.page.getByTestId("add-link-button"); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyIdentityBtn = this.page + .getByTestId("verify-user-link") + .first(); readonly saveDraftBtn = this.page.getByTestId("draft-button"); readonly submitBtn = this.page.getByTestId("submit-button"); diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts index 8a9a59143..20872cf91 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionDetailsPage.ts @@ -23,7 +23,9 @@ export default class ProposalDiscussionDetailsPage { readonly showReplyBtn = this.page.getByTestId("show-more-reply"); readonly closePollYesBtn = this.page.getByTestId("close-the-poll-button"); readonly changeVoteBtn = this.page.getByTestId("change-vote-button"); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyIdentityBtn = this.page + .getByTestId("verify-user-link") + .first(); readonly submitAsGABtn = this.page.getByTestId("submit-as-GA-button"); // Indicators diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts index 231ee041a..c42b966c2 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalDiscussionPage.ts @@ -19,7 +19,9 @@ export default class ProposalDiscussionPage { readonly sortBtn = this.page.getByTestId("sort-button"); readonly searchInput = this.page.getByTestId("search-input"); readonly showAllBtn = this.page.getByTestId("show-all-button").first(); //this.page.getByTestId("show-all-button"); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyIdentityBtn = this.page + .getByTestId("verify-user-link") + .first(); readonly addLinkBtn = this.page.getByTestId("add-link-button"); readonly infoRadio = this.page.getByTestId("info action-radio-wrapper"); readonly treasuryRadio = this.page.getByTestId( diff --git a/tests/govtool-frontend/playwright/lib/pages/proposalSubmissionPage.ts b/tests/govtool-frontend/playwright/lib/pages/proposalSubmissionPage.ts index 1ceb85d65..c06619fa9 100644 --- a/tests/govtool-frontend/playwright/lib/pages/proposalSubmissionPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/proposalSubmissionPage.ts @@ -64,7 +64,9 @@ export default class ProposalSubmissionPage { readonly editSubmissionButton = this.page.getByTestId( "edit-submission-button" ); - readonly verifyIdentityBtn = this.page.getByTestId("verify-identity-button"); + readonly verifyIdentityBtn = this.page + .getByTestId("verify-user-link") + .first(); readonly governanceActionType = this.page.getByLabel( "Governance Action Type *" ); diff --git a/tests/govtool-frontend/playwright/playwright.config.ts b/tests/govtool-frontend/playwright/playwright.config.ts index be00da55a..d0326ef9b 100644 --- a/tests/govtool-frontend/playwright/playwright.config.ts +++ b/tests/govtool-frontend/playwright/playwright.config.ts @@ -97,10 +97,20 @@ export default defineConfig({ name: "proposal discussion", use: { ...devices["Desktop Chrome"] }, testMatch: "**/*.pd.spec.ts", + testIgnore: ["**/*.loggedin.pd.spec.ts"], dependencies: environments.ci ? ["proposal discussion auth setup"] : [], teardown: environments.ci && "cleanup artifacts", + }, + { + name: "proposal discussion (loggedin)", + use: { ...devices["Desktop Chrome"] }, + testMatch: "**/*.loggedin.pd.spec.ts", + dependencies: environments.ci + ? ["proposal discussion auth setup", "user auth setup"] + : [], + teardown: environments.ci && "cleanup artifacts", }, { name: "budget proposal", diff --git a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.dRep.pb.spec.ts b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.dRep.pb.spec.ts index 4c511cf39..0cee8b9d9 100644 --- a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.dRep.pb.spec.ts +++ b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.dRep.pb.spec.ts @@ -25,8 +25,8 @@ test.describe("Budget proposal dRep behaviour", () => { budgetDiscussionDetailsPage = new BudgetDiscussionDetailsPage(page); await budgetDiscussionDetailsPage.goto(proposalId); - await budgetDiscussionDetailsPage.verifyIdentityBtn.click(); - await budgetDiscussionDetailsPage.verifyIdentityBtn.click(); + await budgetDiscussionDetailsPage.verifyUserLink.click(); + await budgetDiscussionDetailsPage.verifyDRepLink.click(); }); test("11K. Should allow registered DRep to vote on a proposal", async () => { @@ -88,10 +88,10 @@ test.describe("Budget proposal dRep behaviour", () => { const comment = faker.lorem.words(5); const budgetDiscussionPage = new BudgetDiscussionPage(page); await budgetDiscussionPage.goto(); - await budgetDiscussionPage.verifyIdentityBtn.click(); - await budgetDiscussionPage.verifyIdentityBtn.click(); + await budgetDiscussionPage.verifyUserLink.click(); const budgetDiscussionDetailsPage = await budgetDiscussionPage.viewFirstProposal(); + await budgetDiscussionPage.verifyDRepLink.click(); await budgetDiscussionDetailsPage.addComment(comment); await expect( diff --git a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.loggedin.pb.spec.ts b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.loggedin.pb.spec.ts index ed705d51a..4b16aefcf 100644 --- a/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.loggedin.pb.spec.ts +++ b/tests/govtool-frontend/playwright/tests/11-proposal-budget/proposalBudget.loggedin.pb.spec.ts @@ -23,7 +23,7 @@ test.describe("Budget proposal logged in state", () => { test.beforeEach(async ({ page }) => { const budgetDiscussionPage = new BudgetDiscussionPage(page); await budgetDiscussionPage.goto(); - await budgetDiscussionPage.verifyIdentityBtn.click(); + await budgetDiscussionPage.verifyUserLink.click(); budgetDiscussionDetailsPage = await budgetDiscussionPage.viewFirstProposal(); }); diff --git a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.pb.spec.ts b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.pb.spec.ts index 24f836706..10db60dd4 100644 --- a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.pb.spec.ts +++ b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.loggedin.pb.spec.ts @@ -45,7 +45,7 @@ test.describe("Budget proposal 01 wallet", () => { }) => { await page.goto("/"); await page.getByTestId("budget-discussion-link").click(); - await page.getByTestId("verify-identity-button").click(); + await page.getByTestId("verify-user-link").first().click(); await expect( page.getByTestId("propose-a-budget-discussion-button") diff --git a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.spec.ts b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.spec.ts index 3ea61045e..304b69234 100644 --- a/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.spec.ts +++ b/tests/govtool-frontend/playwright/tests/12-proposal-budget-submission/proposalBudgetSubmission.spec.ts @@ -15,7 +15,7 @@ test("12A. Should restrict from creating a budget proposal in disconnect state", await page.getByTestId("open-drawer-button").click(); } await page.getByTestId("budget-discussion-link").click(); - await expect(page.getByTestId("verify-identity-button")).not.toBeVisible(); + await expect(page.getByTestId("verify-user-link")).not.toBeVisible(); await expect( page.getByTestId("propose-a-budget-discussion-button") ).not.toBeVisible(); diff --git a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts index 08c1bca6e..938651078 100644 --- a/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts +++ b/tests/govtool-frontend/playwright/tests/6-miscellaneous/miscellaneous.loggedin.spec.ts @@ -97,7 +97,7 @@ test.describe("Logged in user", () => { }) => { await page.goto("/"); await page.getByTestId("proposal-discussion-link").click(); - await page.getByTestId("verify-identity-button").click(); + await page.getByTestId("verify-user-link").first().click(); await expect(page.getByTestId("setup-username-modal")).toBeVisible(); await expect(page.getByTestId("username-input")).toBeVisible(); diff --git a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts index 7f98d5f2d..011627afa 100644 --- a/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts +++ b/tests/govtool-frontend/playwright/tests/8-proposal-discussion/proposalDiscussion.spec.ts @@ -231,7 +231,7 @@ test("8S. Should restrict proposal creation on disconnected state", async ({ const proposalDiscussionPage = new ProposalDiscussionPage(page); await proposalDiscussionPage.goto(); - await expect(proposalDiscussionPage.proposalCreateBtn).not.toBeVisible(); + await expect(proposalDiscussionPage.proposalCreateBtn).toBeDisabled(); }); test("8E. Should share proposed governance action", async ({