diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 316d804a3..0f00ac225 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,14 +7,53 @@ jobs: cypress-run: runs-on: ubuntu-latest steps: + - name: Enable docker.host.internal for Ubuntu + run: | + pwd && sudo bash -c 'echo "172.17.0.1 host.docker.internal" >> /etc/hosts' + - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version-file: '.nvmrc' - cache: 'yarn' + node-version-file: ".nvmrc" + cache: "yarn" + - name: Clone Stack + run: | + git clone -b feat/second-brain-compose https://github.com/stakwork/sphinx-stack.git stack + + - name: Give Permissions to Sphinx Nav Fiber + run: chmod 777 -R relay + + - name: Give Permissions to Stack + working-directory: ./stack + run: | + chmod 777 ./bitcoind; + chmod 777 -R ./relay; + chmod 777 -R ./lnd; + chmod 777 -R ./proxy; + chmod 777 -R ./cln; + + - name: Check for NODES + uses: nick-fields/retry@v2 + with: + timeout_minutes: 10 + max_attempts: 3 + command: | + GITACTION_ENV=gitactionenv docker-compose -f ./stack/alts/navfiber.yml --project-dir ./stack up -d; + sleep 240; + docker ps + docker logs meme.sphinx + docker logs dave.sphinx + docker wait stack_relaysetup_1 + cat stack/relay/NODES.json; + + - name: Copy Node.json + uses: canastro/copy-file-action@master + with: + source: "stack/relay/NODES.json" + target: "relay/nodes.json" - name: Install run: yarn --immutable @@ -26,7 +65,7 @@ jobs: install-command: yarn --immutable browser: chrome start: yarn run start-e2e - wait-on: 'http://localhost:3000' # Waits for above + wait-on: "http://localhost:3000" # Waits for above wait-on-timeout: 120 # Waits for 2 minutes # Records to Cypress Dashboard record: true @@ -38,5 +77,16 @@ jobs: run: yarn run cy-comp continue-on-error: false + - name: Upload Cypress logs + if: failure() + uses: actions/upload-artifact@v2 + with: + name: cypress-logs + path: cypress/videos + - name: Check the coverage value run: yarn test-coverage + + - name: Stop Stack + working-directory: ./stack + run: docker-compose down diff --git a/cypress/e2e/admin/signin.cy.ts b/cypress/e2e/admin/signin.cy.ts new file mode 100644 index 000000000..08b40e6e9 --- /dev/null +++ b/cypress/e2e/admin/signin.cy.ts @@ -0,0 +1,27 @@ +describe('Admin Login', () => { + it('Admin uses the enable function', () => { + cy.visit('http://localhost:3000', { + onBeforeLoad(win) { + win['CYPRESS_USER'] = 'alice' + }, + }) + let title = `Testing NavFiber` + cy.wait(30000) + cy.get('div[data-testid="settings-modal"]').click() + // cy.get('[data-testid="setting-label"]').should('have.text', 'Settings') + cy.get('[data-testid="setting-label"]').should('have.text', 'Admin Settings') + // .invoke('text') + // .then((value) => { + // console.log(value) + // }) + cy.get('#cy-about-title-id').click() + cy.get('#cy-about-title-id').type('{selectAll}') + cy.get('#cy-about-title-id').type(title) + + cy.get('#add-node-submit-cta').click() + cy.wait(200) + cy.get('div[data-testid="close-modal"]').click() + + cy.get('.title').should('have.text', title) + }) +}) diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 6fee2de78..ae0281a3d 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -14,6 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: +import nodes from '../../relay/nodes.json' import './commands' // Alternatively you can use CommonJS syntax: @@ -21,3 +22,28 @@ import './commands' // coverage import '@cypress/code-coverage/support' + +// Add alice as the Second Brain tribe admin +async function setAdmin() { + let user + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].alias === 'alice') { + user = nodes[i] + break + } + } + try { + await fetch('http://localhost:8444/api/set_admin_pubkey', { + method: 'POST', + headers: { 'x-admin-token': 'navfiber_e2e_testing', 'Content-Type': 'application/json' }, + body: JSON.stringify({ + pubkey: user.pubkey, + name: user.alias, + }), + }) + } catch (error) { + console.error(JSON.stringify(error)) + } +} + +setAdmin() diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 18edb199a..1b6425b80 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -2,7 +2,9 @@ "compilerOptions": { "target": "es5", "lib": ["es5", "dom"], - "types": ["cypress", "node"] + "types": ["cypress", "node"], + "resolveJsonModule": true, + "esModuleInterop": true }, "include": ["**/*.ts"] } diff --git a/package.json b/package.json index 8fd6ad99c..d2b4988a5 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ }, "scripts": { "start": "vite --port 3000", - "start-e2e": "VITE_APP_IS_E2E=true vite --port 3000", + "start-e2e": "VITE_APP_IS_E2E=true VITE_APP_API_URL=http://localhost:8444/api vite --host --port 3000", "dev-tribe": "vite --port 3004", "build": "tsc && vite build", "build-docker": "tsc && vite build", diff --git a/relay/nodes.json b/relay/nodes.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/relay/nodes.json @@ -0,0 +1 @@ +[] diff --git a/src/components/App/SideBar/Show/index.tsx b/src/components/App/SideBar/Show/index.tsx index c53f211bd..31b0cf2e9 100644 --- a/src/components/App/SideBar/Show/index.tsx +++ b/src/components/App/SideBar/Show/index.tsx @@ -1,10 +1,10 @@ import { useMemo, useState } from 'react' import styled from 'styled-components' -import { useGraphData } from '~/components/DataRetriever' import { Avatar } from '~/components/common/Avatar' import { Flex } from '~/components/common/Flex' import { Text } from '~/components/common/Text' import { TypeBadge } from '~/components/common/TypeBadge' +import { useGraphData } from '~/components/DataRetriever' import { useDataStore } from '~/stores/useDataStore' import { NodeExtended } from '~/types' import { getSelectedNodeTimestamps } from '~/utils' diff --git a/src/components/Auth/index.tsx b/src/components/Auth/index.tsx index a5ed308d1..441ed4baf 100644 --- a/src/components/Auth/index.tsx +++ b/src/components/Auth/index.tsx @@ -7,7 +7,8 @@ import { Text } from '~/components/common/Text' import { isDevelopment, isE2E } from '~/constants' import { getIsAdmin } from '~/network/auth' import { useUserStore } from '~/stores/useUserStore' -import { executeIfProd, getSignedMessageFromRelay, updateBudget } from '~/utils' +import { sphinxBridge } from '~/testSphinxBridge' +import { getSignedMessageFromRelay, updateBudget } from '~/utils' interface setAuthenticated { setAuthenticated: (state: boolean) => void @@ -18,49 +19,55 @@ export const Auth = ({ setAuthenticated }: setAuthenticated) => { const [setBudget, setIsAdmin, setPubKey] = useUserStore((s) => [s.setBudget, s.setIsAdmin, s.setPubKey]) const handleAuth = useCallback(async () => { - await executeIfProd(async () => { - localStorage.removeItem('admin') + // await executeIfProd(async () => { + localStorage.removeItem('admin') - try { + let sphinxEnable + + try { + if (!isE2E) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const sphinxEnable = await sphinx.enable() - - sessionStorage.setItem('isSphinx', sphinxEnable ? 'true' : 'false') - - setPubKey(sphinxEnable?.pubkey) - } catch (error) { - setPubKey('') + sphinxEnable = await sphinx.enable() + } else { + sphinxEnable = await sphinxBridge.enable() } - await updateBudget(setBudget) + sessionStorage.setItem('isSphinx', sphinxEnable ? 'true' : 'false') - try { - const sigAndMessage = await getSignedMessageFromRelay() + setPubKey(sphinxEnable?.pubkey) + } catch (error) { + setPubKey('') + } - const res = await getIsAdmin({ - message: sigAndMessage.message, - signature: sigAndMessage.signature, - }) + await updateBudget(setBudget) - if (!res.data.isPublic && !res.data.isAdmin && !res.data.isMember) { - setUnauthorized(true) + try { + const sigAndMessage = await getSignedMessageFromRelay() - return - } + const res = await getIsAdmin({ + message: sigAndMessage.message, + signature: sigAndMessage.signature, + }) - if (res.data.isAdmin) { - localStorage.setItem('admin', JSON.stringify({ isAdmin: true })) - setIsAdmin(true) - } + if (!res.data.isPublic && !res.data.isAdmin && !res.data.isMember) { + setUnauthorized(true) - setAuthenticated(true) + return + } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - /* not an admin */ + if (res.data.isAdmin) { + localStorage.setItem('admin', JSON.stringify({ isAdmin: true })) + setIsAdmin(true) } - }) + + setAuthenticated(true) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + /* not an admin */ + } + // }) if (isE2E || isDevelopment) { setAuthenticated(true) diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 9081c3eb6..ebd8d9fc6 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -138,7 +138,7 @@ export const BaseModal = ({ py={noWrap ? 0 : 20} > {onClose && ( - + )} diff --git a/src/components/SettingsModal/SettingsView/index.tsx b/src/components/SettingsModal/SettingsView/index.tsx index 0f2ab6cf2..9c5cb507b 100644 --- a/src/components/SettingsModal/SettingsView/index.tsx +++ b/src/components/SettingsModal/SettingsView/index.tsx @@ -55,7 +55,7 @@ export const SettingsView: React.FC = ({ onClose }) => { const SettingsHeader = ({ children }: { children: React.ReactNode }) => ( - {getSettingsLabel()} + {getSettingsLabel()} {resolveAdminActions()} {children} diff --git a/src/components/Universe/Graph/Particles/index.tsx b/src/components/Universe/Graph/Particles/index.tsx index 3a4b07b4c..9160d5b8b 100644 --- a/src/components/Universe/Graph/Particles/index.tsx +++ b/src/components/Universe/Graph/Particles/index.tsx @@ -7,8 +7,8 @@ export const Particles: React.FC = () => { const ref = useRef(null) useFrame(() => { - const positions = (ref.current?.geometry.getAttribute('position') as THREE.BufferAttribute).array as Float32Array - const velocities = (ref.current?.geometry.getAttribute('velocity') as THREE.BufferAttribute).array as Float32Array + const positions = (ref.current?.geometry.getAttribute('position') as THREE.BufferAttribute)?.array as Float32Array + const velocities = (ref.current?.geometry.getAttribute('velocity') as THREE.BufferAttribute)?.array as Float32Array if (positions && velocities) { for (let i = 0; i < positions.length; i += 3) { diff --git a/src/testSphinxBridge/enable.ts b/src/testSphinxBridge/enable.ts new file mode 100644 index 000000000..2c251f8f8 --- /dev/null +++ b/src/testSphinxBridge/enable.ts @@ -0,0 +1,11 @@ +import { getCurrentUser } from './helper' + +export function enableSphinx() { + const user = getCurrentUser() + + if (!user) { + return null + } + + return { budget: 0, pubkey: user.pubkey } +} diff --git a/src/testSphinxBridge/getLsat.ts b/src/testSphinxBridge/getLsat.ts new file mode 100644 index 000000000..56754c332 --- /dev/null +++ b/src/testSphinxBridge/getLsat.ts @@ -0,0 +1,47 @@ +import { getCurrentUser } from './helper' + +export async function getLsatSphinx(host: string) { + try { + const user = getCurrentUser() + + if (user) { + const result = await fetch(`${user.external_ip}/active_lsat?issuer=${host}`, { + headers: { 'x-user-token': user.authToken }, + }) + + const res = await result.json() + + if (res.success) { + const { lsat } = res.response + + return { + paymentRequest: lsat.paymentRequest, + macaroon: lsat.macaroon, + issuer: lsat.issuer, + identifier: lsat.identifier, + preimage: lsat.preimage, + paths: lsat.paths, + status: lsat.status, + success: false, + } + } + + return { + paymentRequest: '', + macaroon: '', + issuer: '', + identifier: '', + preimage: '', + paths: '', + status: '', + success: false, + } + } + + return null + } catch (error) { + console.log(JSON.stringify(error)) + + return null + } +} diff --git a/src/testSphinxBridge/helper.ts b/src/testSphinxBridge/helper.ts new file mode 100644 index 000000000..688fc43c7 --- /dev/null +++ b/src/testSphinxBridge/helper.ts @@ -0,0 +1,29 @@ +import { RelayUser } from '~/types' +import nodes from '../../relay/nodes.json' + +export function getCurrentUser(): RelayUser | null { + /* eslint-disable @typescript-eslint/no-explicit-any */ + if ((window as any).CYPRESS_USER) { + const user = (window as any).CYPRESS_USER + + let userNode + + for (let i = 0; i < nodes.length; i += 1) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (nodes[i].alias === user) { + userNode = nodes[i] + + break + } + } + + if (!userNode) { + return null + } + + return userNode + } + + return null +} diff --git a/src/testSphinxBridge/index.ts b/src/testSphinxBridge/index.ts new file mode 100644 index 000000000..2b6fd4c6d --- /dev/null +++ b/src/testSphinxBridge/index.ts @@ -0,0 +1,9 @@ +import { enableSphinx } from './enable' +import { getLsatSphinx } from './getLsat' +import { signMessage } from './signMessage' + +export const sphinxBridge = { + enable: async () => enableSphinx(), + getLsat: async (host: string) => getLsatSphinx(host), + signMessage: async (message: string) => signMessage(message), +} diff --git a/src/testSphinxBridge/signMessage.ts b/src/testSphinxBridge/signMessage.ts new file mode 100644 index 000000000..df8d5a45e --- /dev/null +++ b/src/testSphinxBridge/signMessage.ts @@ -0,0 +1,15 @@ +import { getCurrentUser } from './helper' + +export async function signMessage(message: string) { + const user = await getCurrentUser() + + if (user) { + const result = await fetch(`${user.external_ip}/signer/${message}`, { + headers: { 'x-user-token': user.authToken }, + }) + + return result.json() + } + + throw new Error('Cypress User not set') +} diff --git a/src/types/index.ts b/src/types/index.ts index e4c0e0f71..22dd9a594 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -243,3 +243,19 @@ export type TStats = { numVideo?: string numDocuments?: string } + +export type RelayUser = { + alias: string + pubkey: string + ip: string + external_ip: string + authToken: string + transportToken: string + contact_key: string + privkey: string + exported_keys: string + pin: string + proxy_ip?: string + admin_token?: string + routeHint?: string +} diff --git a/src/utils/getLSat/index.ts b/src/utils/getLSat/index.ts index 3eabfdfa0..73eff5bd7 100644 --- a/src/utils/getLSat/index.ts +++ b/src/utils/getLSat/index.ts @@ -1,4 +1,6 @@ import * as sphinx from 'sphinx-bridge' +import { isE2E } from '~/constants' +import { sphinxBridge } from '~/testSphinxBridge' import { isSphinx } from '../isSphinx' /** @@ -22,9 +24,17 @@ export const getLSat = async (): Promise => { // Check if sphinx app is active if (isSphinx()) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const storedLsat = await sphinx.getLsat(window.location.host) + let storedLsat + + const { host } = window.location + + if (!isE2E) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + storedLsat = await sphinx.getLsat(host) + } else { + storedLsat = await sphinxBridge.getLsat(host) + } if (storedLsat.macaroon) { window.localStorage.setItem( diff --git a/src/utils/getSignedMessage/index.ts b/src/utils/getSignedMessage/index.ts index af7520637..5e68507ef 100644 --- a/src/utils/getSignedMessage/index.ts +++ b/src/utils/getSignedMessage/index.ts @@ -1,4 +1,6 @@ import * as sphinx from 'sphinx-bridge' +import { isE2E } from '~/constants' +import { sphinxBridge } from '~/testSphinxBridge' import { isSphinx } from '../isSphinx' // queue for sphinx, it handles only on request at a time @@ -16,23 +18,41 @@ export async function getSignedMessageFromRelay(): Promise<{ message: string; si if (isSphinx()) { if (!signingPromise) { - signingPromise = sphinx - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - .signMessage(message) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .then((storedLsat: any) => { - signingPromise = null // Reset the promise after it's resolved - - return { message, signature: storedLsat.signature } - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .catch((error: any) => { - signingPromise = null // Reset the promise on error - console.error(error) - - return { message: '', signature: '' } - }) + if (!isE2E) { + signingPromise = sphinx + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + .signMessage(message) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then((storedLsat: any) => { + signingPromise = null // Reset the promise after it's resolved + + return { message, signature: storedLsat.signature } + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .catch((error: any) => { + signingPromise = null // Reset the promise on error + console.error(error) + + return { message: '', signature: '' } + }) + } else { + signingPromise = sphinxBridge + .signMessage(message) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then((storedLsat: any) => { + signingPromise = null // Reset the promise after it's resolved + + return { message, signature: storedLsat.response.sig } + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .catch((error: any) => { + signingPromise = null // Reset the promise on error + console.error(error) + + return { message: '', signature: '' } + }) + } } return signingPromise