Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brave-flies-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/mobile": patch
---

✅ test card activation flow
5 changes: 5 additions & 0 deletions .changeset/common-drinks-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

🤡 enhance panda mocks for e2e tests
5 changes: 5 additions & 0 deletions .changeset/hot-buckets-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/mobile": patch
---

♿️ add aria label to card sensitive toggle
5 changes: 5 additions & 0 deletions .changeset/kind-yaks-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/mobile": patch
---

✅ extract maestro aria subflows
35 changes: 10 additions & 25 deletions .maestro/flows/local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,19 @@ tags: [critical]
file: ../subflows/sendAsset.yaml
env: { asset: USDC, to: "${output.owner}", amount: "69" }
- tapOn: Home
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { true: "${maestro.platform != 'web'}" }
commands:
- repeat:
while: { visible: Pending proposals }
commands: [{ tapOn: Home }]
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { platform: web }
commands:
- repeat:
while: { visible: { id: Pending proposals } }
commands: [{ tapOn: Home }]
- runFlow:
file: ../subflows/tapWhileAria.yaml
env: { aria: Pending proposals, tap: Home }
- runFlow: ../subflows/readPortfolio.yaml
- assertTrue: ${output.portfolioBefore - output.portfolio === 69}
- evalScript: ${output.portfolioBefore = output.portfolio}
- runFlow:
file: ../subflows/borrowAsset.yaml
env: { amount: "10", installments: "4" }
env: { amount: "10", installments: "1" }
- tapOn: Home
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { true: "${maestro.platform != 'web'}" }
commands:
- repeat:
while: { visible: Pending proposals }
commands: [{ tapOn: Home }]
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { platform: web }
commands:
- repeat:
while: { visible: { id: Pending proposals } }
commands: [{ tapOn: Home }]
- runFlow:
file: ../subflows/tapWhileAria.yaml
env: { aria: Pending proposals, tap: Home }
- runFlow: ../subflows/readPortfolio.yaml
- assertTrue: ${output.portfolio - output.portfolioBefore === 10}
- runFlow:
Expand All @@ -72,4 +54,7 @@ tags: [critical]
- tapOn: View all steps
- runFlow: ../subflows/verifyIdentity.yaml
- assertNotVisible: Getting Started
- runFlow: ../subflows/activateCard.yaml
- tapOn: Home
- assertVisible: SPENDING LIMIT
- runFlow: ../subflows/storeCoverage.yaml
43 changes: 43 additions & 0 deletions .maestro/subflows/activateCard.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
appId: ${APP_ID ?? "app.exactly"}
---
- tapOn: Card
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands:
- tapOn: \$[\s\d,.\xa0]+, AVAILABLE BALANCE
- runFlow:
when: { platform: web }
commands: [{ tapOn: Available balance }]
- runFlow:
when: { visible: Accept and enable card }
commands: [{ tapOn: Accept and enable card }]
- assertVisible: Manually add your card to Apple Pay & Google Pay to make contactless payments.
- tapOn: Close
- tapOn: Freeze card
- tapOn: Unfreeze card
- tapOn: View PIN number
- tapOn: Close
- tapOn: Weekly spending limit
- tapOn: Close
- runFlow: { file: ../subflows/tapAria.yaml, env: { aria: Hide sensitive } }
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands:
- assertNotVisible: \$[\s\d,.\xa0]+, AVAILABLE BALANCE
- runFlow:
when: { platform: web }
commands:
- assertNotVisible:
text: \$[\s\d,.\xa0]+
above: Available balance
- runFlow: { file: ../subflows/tapAria.yaml, env: { aria: Show sensitive } }
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands:
- assertVisible: \$[\s\d,.\xa0]+, AVAILABLE BALANCE
- runFlow:
when: { platform: web }
commands:
- assertVisible:
text: \$[\s\d,.\xa0]+
above: Available balance
7 changes: 1 addition & 6 deletions .maestro/subflows/borrowAsset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ appId: ${APP_ID ?? "app.exactly"}
commands: [{ tapOn: Connect wallet to Exactly Protocol }]
- tapOn: Explore funding options
- assertVisible: Select amount
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { true: "${maestro.platform != 'web'}" }
commands: [{ tapOn: Amount }]
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { platform: web }
commands: [{ tapOn: { id: Amount } }]
- runFlow: { file: tapAria.yaml, env: { aria: Amount } }
- inputText: ${amount}
- tapOn: Select Amount # HACK
- tapOn: Continue
Expand Down
7 changes: 1 addition & 6 deletions .maestro/subflows/sendAsset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,4 @@ appId: ${APP_ID ?? "app.exactly"}
- runScript:
file: ../dist/hookProposals.js
env: { account: "${output.account}" }
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands: [{ tapOn: Close }]
- runFlow: # HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
when: { platform: web }
commands: [{ tapOn: { id: Close } }]
- runFlow: { file: tapAria.yaml, env: { aria: Close } }
5 changes: 1 addition & 4 deletions .maestro/subflows/storeCoverage.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
appId: ${APP_ID ?? "app.exactly"}
---
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands: [{ tapOn: Settings }]
- runFlow: { when: { platform: web }, commands: [{ tapOn: { id: Settings } }] }
- runFlow: { file: tapAria.yaml, env: { aria: Settings } }
- tapOn: Submit coverage
- runFlow:
when: { platform: android }
Expand Down
9 changes: 9 additions & 0 deletions .maestro/subflows/tapAria.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
appId: ${APP_ID ?? "app.exactly"}
---
# HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands: [{ tapOn: "${aria}" }]
- runFlow:
when: { platform: web }
commands: [{ tapOn: { id: "${aria}" } }]
Comment thread
cruzdanilo marked this conversation as resolved.
15 changes: 15 additions & 0 deletions .maestro/subflows/tapWhileAria.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
appId: ${APP_ID ?? "app.exactly"}
---
# HACK https://github.com/mobile-dev-inc/Maestro/issues/2914
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands:
- repeat:
while: { visible: "${aria}" }
commands: [{ tapOn: "${tap ?? aria}" }]
Comment thread
cruzdanilo marked this conversation as resolved.
- runFlow:
when: { platform: web }
commands:
- repeat:
while: { visible: { id: "${aria}" } }
commands: [{ tapOn: "${tap ?? aria}" }]
Comment thread
cruzdanilo marked this conversation as resolved.
5 changes: 1 addition & 4 deletions .maestro/subflows/verifyIdentity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,5 @@ appId: ${APP_ID ?? "app.exactly"}
- runScript:
file: ../dist/approveKYC.js
env: { credentialId: "${output.owner}" }
- runFlow:
when: { true: "${maestro.platform != 'web'}" }
commands: [{ tapOn: Back }]
- runFlow: { when: { platform: web }, commands: [{ tapOn: { id: Back } }] }
- runFlow: { file: tapAria.yaml, env: { aria: Back } }
- tapOn: Home
10 changes: 8 additions & 2 deletions common/pandaCertificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ GE+OO1j3fa8HhYlJZZ7CCIAsaCorrU+ZpD5PUTnmME3DJk+JyY1BB3p8XI+C5uno
QucrbxFbkM1lgR10ewz/LcuhleG0mrXL/bzUZbeJqI6v3c9bXvLPKlsordPanYBG
FZkmBPxc8QEdRgH4awIDAQAB
-----END PUBLIC KEY-----`,
}[domain] ||
`-----BEGIN PUBLIC KEY-----
"sandbox.exactly.app": `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAP192809jZyaw62g/eTzJ3P9H
+RmT88sXUYjQ0K8Bx+rJ83f22+9isKx+lo5UuV8tvOlKwvdDS/pVbzpG7D7NO45c
0zkLOXwDHZkou8fuj8xhDO5Tq3GzcrabNLRLVz3dkx0znfzGOhnY4lkOMIdKxlQb
LuVM/dGDC9UpulF+UwIDAQAB
-----END PUBLIC KEY-----`,
}[domain] ||
`-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu2YOeObkaYiQmc49t2Cnk8syA
1UBqFBMVhkJXyuSA9f+hGC22fXgQtpfAjQmFRpt5q4f6i0rG2bUi8Km0jZELdD6X
Kz63/hp522fbxNuOOxs37dlH9B3k6W8NQjjDjaFhAwCsevq7uASXwEEK3NpV7DEP
lJe6c8CQ0+QqTTy2ZwIDAQAB
-----END PUBLIC KEY-----`;
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
89 changes: 83 additions & 6 deletions server/test/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import "./mocks/sardine";
import "./mocks/sentry";

import { cors } from "hono/cors";
import crypto from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import { describe, expect, it, vi } from "vitest";

import type * as panda from "../utils/panda";
import type * as persona from "../utils/persona";
import type * as sentry from "@sentry/node";

Expand Down Expand Up @@ -43,12 +45,87 @@ describe("e2e", () => {
);
});

vi.mock("../utils/panda", async (importOriginal) => ({
...(await importOriginal()),
createUser: vi
.fn<() => Promise<{ id: string }>>()
.mockImplementation(() => Promise.resolve({ id: String(Math.random()) })),
}));
vi.mock("../utils/panda", async (importOriginal: () => Promise<typeof panda>) => {
const original = await importOriginal();
type User = Awaited<ReturnType<typeof original.getUser>>;
type Card = Awaited<ReturnType<typeof original.getCard>>;
const users = new Map<string, User>();
const cards = new Map<string, Card>();
return {
...original,
autoCredit: vi.fn().mockResolvedValue(false),
createCard: vi.fn().mockImplementation((userId: string) => {
const id = `crd_${Math.random().toString(36).slice(2)}`;
const card: Card = {
expirationMonth: "12",
expirationYear: "2030",
id,
last4: String(Math.floor(1000 + Math.random() * 9000)),
limit: { amount: 1_000_000, frequency: "per7DayPeriod" },
status: "active",
type: "virtual",
userId,
};
cards.set(id, card);
return Promise.resolve(card);
}),
createUser: vi.fn().mockImplementation(() => {
const id = `usr_${Math.random().toString(36).slice(2)}`;
const user: User = {
applicationReason: "",
applicationStatus: "approved",
email: "test@example.com",
firstName: "Test",
id,
isActive: true,
lastName: "User",
phoneCountryCode: "+1",
phoneNumber: "5551234567",
};
users.set(id, user);
return Promise.resolve({ id });
}),
getCard: vi.fn().mockImplementation((cardId: string) => Promise.resolve(cards.get(cardId))),
Comment thread
cruzdanilo marked this conversation as resolved.
Comment thread
cruzdanilo marked this conversation as resolved.
getPIN: vi.fn().mockResolvedValue({ pin: null }),
getSecrets: vi.fn().mockImplementation((_cardId: string, sessionId: string) => {
const privateKey = process.env.PANDA_E2E_PRIVATE_KEY;
if (!privateKey) throw new Error("PANDA_E2E_PRIVATE_KEY not set");
const encryptedSecret = Buffer.from(sessionId, "base64");
const secretKeyBase64 = crypto.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha1" },
encryptedSecret,
);
const secretKey = Buffer.from(secretKeyBase64.toString("utf8"), "base64");
function encrypt(plaintext: string) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-128-gcm", secretKey, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
return { data: Buffer.concat([encrypted, authTag]).toString("base64"), iv: iv.toString("base64") };
}
return Promise.resolve({
encryptedCvc: encrypt("123"),
encryptedPan: encrypt("4111111111111234"),
});
}),
getUser: vi.fn().mockImplementation((userId: string) => Promise.resolve(users.get(userId))),
isPanda: vi.fn().mockResolvedValue(true),
setPIN: vi.fn().mockResolvedValue({}),
signIssuerOp: vi.fn().mockResolvedValue("0x" + "ab".repeat(65)),
updateCard: vi.fn().mockImplementation((update: { id: string }) => {
const card = cards.get(update.id);
if (!card) return Promise.resolve();
Object.assign(card, update);
return Promise.resolve(card);
}),
updateUser: vi.fn().mockImplementation((update: { id: string }) => {
const user = users.get(update.id);
if (!user) return Promise.resolve();
Object.assign(user, update);
return Promise.resolve(user);
}),
Comment thread
cruzdanilo marked this conversation as resolved.
};
});

vi.mock("../utils/persona", async (importOriginal: () => Promise<typeof persona>) => {
const original = await importOriginal();
Expand Down
16 changes: 16 additions & 0 deletions server/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ export default defineConfig({
KEEPER_PRIVATE_KEY: padHex("0x69"),
PANDA_API_KEY: "panda",
PANDA_API_URL: "https://panda.test",
PANDA_E2E_PRIVATE_KEY: `-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK7Zg545uRpiJCZz
j23YKeTyzIDVQGoUExWGQlfK5ID1/6EYLbZ9eBC2l8CNCYVGm3mrh/qLSsbZtSLw
qbSNkQt0PpcrPrf+GnnbZ9vE2447Gzft2Uf0HeTpbw1COMONoWEDAKx6+ru4BJfA
QQrc2lXsMQ+Ul7pzwJDT5CpNPLZnAgMBAAECgYA7bxqLPSnLaxLIsz1M5E6RUWrs
XBCyPjKifWmtt/zmTThghPx87LdUTwzUWdyjnfWZbRIiuxhm8Xfd8ZpuEjT79H0j
4GT12UOFlKAvi2lXdqn7IBFIkdVC3kS6wFUFbHKTGwiVDUP/l9z92POPEV+cIpAh
rf1q7VYxoOXU2+RBUQJBAORq7ebGfLgnvNp49o8bSUcNxbhola7jpRCKOt2oexAM
B3SmvX/hPlthst3Lcpa/vYE5VFvLqa1DnObBoDWwvV8CQQDD9qM9W7HmMgByUv9S
3Fs/Qqqe7dhIlcXD3mLjby2vxH5qE1+okNgFcTgJ/G0oacFz3uUhbvYAqFWU3LIh
1Jv5AkBB11zKF87dmn7CjvmrWJcvxxWGSYdUCUSMVvwO5sDKaF1Bz8px8TBzUN8p
NbrLH2v1stvRNgyr6ABzN78BmveLAkEAqMVzA7ZEOghYYB3hLgEASTRmdChN/P2Y
7L9MFaq8A0RMx5jV6vyMP+upotgXPxYN+Xg/iJLjJd/UjTeh5wcQKQJAKkzcVyZw
OEW5okJDZmmfTeh96WBhKGaOczZuuYn88I3A6cKj1p8Yc7UZ1X8vvztY5P7N0YbL
VuNOZKwaXFtqgA==
-----END PRIVATE KEY-----`,
Comment thread
cruzdanilo marked this conversation as resolved.
PAX_API_KEY: "pax",
PAX_API_URL: "https://pax.test",
PAX_ASSOCIATE_ID_KEY: "pax",
Expand Down
1 change: 1 addition & 0 deletions src/components/card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ export default function Card() {
</Text>
<View display="flex" flexDirection="row" alignItems="center" gap={16}>
<Pressable
aria-label={hidden ? t("Show sensitive") : t("Hide sensitive")}
onPress={() => {
queryClient.setQueryData(["settings", "sensitive"], !hidden);
}}
Expand Down