diff --git a/.env.docker b/.env.docker new file mode 100644 index 000000000..4cd0fc85a --- /dev/null +++ b/.env.docker @@ -0,0 +1,5 @@ +VUE_APP_NETWORK_NAME=Local network +VUE_APP_NODE_URL=http://localhost:3013 +VUE_APP_MIDDLEWARE_URL=http://localhost:4000 +VUE_APP_EXPLORER_URL=http://localhost:3070 +VUE_APP_BACKEND_URL=http://localhost:3079 diff --git a/.github/workflows/_validate-npm.yml b/.github/workflows/_validate-npm.yml index 37fe98909..f6a315781 100644 --- a/.github/workflows/_validate-npm.yml +++ b/.github/workflows/_validate-npm.yml @@ -15,11 +15,13 @@ jobs: - run: npm test working-directory: 'backend' - run: npm ci --legacy-peer-deps # TODO: remove --legacy-peer-deps after updating dependencies + - run: docker compose up middleware -d --wait + - run: ./docker-compose/init-state.mjs - run: npm test - uses: stefanzweifel/git-auto-commit-action@v5 if: failure() with: commit_message: "fixme: update e2e screenshots" file_pattern: 'tests/e2e/*.png' - - run: docker-compose logs + - run: docker compose logs if: always() diff --git a/backend/src/server.js b/backend/src/server.js index 42b64a06b..58c67fb27 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -164,7 +164,7 @@ export default (port, log = () => {}) => { const interval = setInterval(() => { const s = getStats(); log(`Connected ${s.clients} clients, recorded ${s.leaders} leaders, ${s.followers} followers`); - }, 20000); + }, 300000); const ioClose = io.close; io.close = function closeHandler(...args) { log('Stopping server'); diff --git a/docker-compose.yml b/docker-compose.yml index a44928b44..b44f8a2b4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,32 @@ -version: '3' services: + middleware: + # TODO: use upstream after merging https://github.com/aeternity/ae_mdw/issues/1758 + image: davidyuk/temp:mdw-devmode + ports: [4000:4000, 4001:4001, 3013:3013, 3313:3313] + volumes: + - ./docker-compose/aeternity.yaml:/home/aeternity/aeternity.yaml + stop_grace_period: 0s + + explorer: + # TODO: use upstream after merging https://github.com/aeternity/aescan/pull/774 + image: davidyuk/temp:explorer + ports: [3070:80] + environment: + - NUXT_PUBLIC_NETWORK_NAME=Local network + - NUXT_PUBLIC_NODE_URL=http://host.docker.internal:3013 + - NUXT_PUBLIC_MIDDLEWARE_URL=http://host.docker.internal:4000 + - NUXT_PUBLIC_WEBSOCKET_URL=ws://host.docker.internal:4001/v2/websocket + frontend: - image: aepp-base - build: - context: . - ports: ["3080:80"] + build: . + ports: [3080:80] environment: + - VUE_APP_NETWORK_NAME=Local network + - VUE_APP_NODE_URL=http://localhost:3013 + - VUE_APP_MIDDLEWARE_URL=http://localhost:4000 + - VUE_APP_EXPLORER_URL=http://localhost:3070 - VUE_APP_BACKEND_URL=http://localhost:3079 + backend: - image: aepp-base-backend build: backend - ports: ["3079:80"] + ports: [3079:80] diff --git a/docker-compose/aeternity.yaml b/docker-compose/aeternity.yaml new file mode 100644 index 000000000..f9342b50c --- /dev/null +++ b/docker-compose/aeternity.yaml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://github.com/aeternity/aeternity/raw/master/apps/aeutils/priv/aeternity_config_schema.json + +system: + dev_mode: true + plugins: + - name: aeplugin_dev_mode + +dev_mode: + auto_emit_microblocks: true + +chain: + persist: false + hard_forks: + "1": 0 + "6": 1 + genesis_accounts: + ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E: 10000000000000000000000 + +# TODO: remove after solving https://github.com/aeternity/ae_mdw/issues/1760 +fork_management: + network_id: ae_dev + +# TODO remove after solving https://github.com/aeternity/ae_mdw/issues/1760#issuecomment-2102872638 +mining: + beneficiary: ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E diff --git a/docker-compose/init-state.mjs b/docker-compose/init-state.mjs new file mode 100755 index 000000000..7a406d639 --- /dev/null +++ b/docker-compose/init-state.mjs @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +import { execSync } from 'child_process'; +import { + Node, AeSdk, MemoryAccount, generateSaveHDWalletFromSeed, getSaveHDWalletAccounts, +} from '@aeternity/aepp-sdk-next'; +import { mnemonicToSeed } from '@aeternity/bip39'; + +// TODO: remove after merging https://github.com/aeternity/ae_mdw/issues/1758 +try { + execSync( + 'docker compose exec middleware ./bin/ae_mdw rpc ":aeplugin_dev_mode_app.start_unlink()"', + { stdio : 'pipe' }, + ); +} catch (error) { + if (!error.message.includes('{:error, {:already_started')) throw error; +} + +await (async function rollbackToFirstBlock() { + const { status } = await fetch('http://localhost:3313/rollback?height=1'); + if (status !== 200) throw new Error(`Unexpected status code: ${status}`); +})(); + +const aeSdk = new AeSdk({ + nodes: [{ name: 'testnet', instance: new Node('http://localhost:3013') }], + accounts: [ + new MemoryAccount('9ebd7beda0c79af72a42ece3821a56eff16359b6df376cf049aee995565f022f840c974b97164776454ba119d84edc4d6058a8dec92b6edc578ab2d30b4c4200'), + ], +}); + +const seed = mnemonicToSeed('cross cat upper state flame wire inner betray almost party agree endorse'); +const wallet = generateSaveHDWalletFromSeed(seed, ''); +const [{ publicKey, secretKey }] = getSaveHDWalletAccounts(wallet, '', 1); + +await aeSdk.spend(1e20, publicKey); + +await (async function prepareTransactionHistory() { + const onAccount = new MemoryAccount(secretKey); + for (let i = 0; i < 15; i += 1) { + await aeSdk.spend(1e14, aeSdk.address, { onAccount }); + } +})(); diff --git a/package.json b/package.json index f4f8bf158..59fd85515 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build:android": "npm run build:cordova && cordova build android", "lint": "NODE_ENV=production vue-cli-service lint . --no-fix", "test": "npm run lint && npm run test:unit && npm run test:e2e -- --headless", - "test:e2e": "vue-cli-service test:e2e", + "test:e2e": "vue-cli-service test:e2e --mode docker", "test:unit": "vue-cli-service test:unit", "i18n:report": "vue-i18n-extract report --vueFiles './src/**/*.?(js|vue)' --languageFiles './src/locales/**/*.json'" }, diff --git a/src/store/plugins/initSdk.js b/src/store/plugins/initSdk.js index 2d37fb541..47f6ae6a3 100644 --- a/src/store/plugins/initSdk.js +++ b/src/store/plugins/initSdk.js @@ -127,7 +127,16 @@ export default (store) => { delete spec.paths['/names/pointees/{id}']; return genSwaggerClient(specUrl, { spec }); })(), - genSwaggerClient(`${network.middlewareUrl}/v2/api`), + (async () => { + const specUrl = `${network.middlewareUrl}/v2/api`; + const spec = await fetchJson(specUrl); + // TODO: remove after solving https://github.com/aeternity/ae_mdw/issues/1759 + if (network.middlewareUrl === 'http://localhost:4000') { + spec.servers[0].url = spec.servers[0].url.replace('/mdw', ''); + } + spec.paths['/status'].parameters ??= []; // bug in @aeternity/aepp-sdk@11.0.1 + return genSwaggerClient(specUrl, { spec }); + })(), ]); // TODO: remove after updating sdk sdk.Ae.defaults.verify = false; diff --git a/tests/e2e/fixtures/state-created-mnemonic-not-backup.json b/tests/e2e/fixtures/state-created-mnemonic-not-backup.json index 88d926e9e..baa460d80 100644 --- a/tests/e2e/fixtures/state-created-mnemonic-not-backup.json +++ b/tests/e2e/fixtures/state-created-mnemonic-not-backup.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/fixtures/state-created-mnemonic.json b/tests/e2e/fixtures/state-created-mnemonic.json index 06e139a23..6c83368c9 100644 --- a/tests/e2e/fixtures/state-created-mnemonic.json +++ b/tests/e2e/fixtures/state-created-mnemonic.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/fixtures/state-created.json b/tests/e2e/fixtures/state-created.json index 454b3317d..1eb94423c 100644 --- a/tests/e2e/fixtures/state-created.json +++ b/tests/e2e/fixtures/state-created.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/fixtures/state-encrypted-test.json b/tests/e2e/fixtures/state-encrypted-test.json index 65c02ab6b..a041f20ff 100644 --- a/tests/e2e/fixtures/state-encrypted-test.json +++ b/tests/e2e/fixtures/state-encrypted-test.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/fixtures/state-encrypted.json b/tests/e2e/fixtures/state-encrypted.json index ad7ae1d5e..b04d4a7a6 100644 --- a/tests/e2e/fixtures/state-encrypted.json +++ b/tests/e2e/fixtures/state-encrypted.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/fixtures/state-recovered.json b/tests/e2e/fixtures/state-recovered.json index 2b30d050f..b244c513b 100644 --- a/tests/e2e/fixtures/state-recovered.json +++ b/tests/e2e/fixtures/state-recovered.json @@ -15,7 +15,7 @@ "activeCode": "eur", "swapped": false }, - "sdkUrl": "https://mainnet.aeternity.io", + "sdkUrl": "http://localhost:3013", "names": { "defaults": {} }, diff --git a/tests/e2e/specs/settings/mnemonic.cy.js b/tests/e2e/specs/settings/mnemonic.cy.js index 2dbea8500..1960f9131 100644 --- a/tests/e2e/specs/settings/mnemonic.cy.js +++ b/tests/e2e/specs/settings/mnemonic.cy.js @@ -41,7 +41,7 @@ describe('Settings mnemonic', () => { cy.get('.notification-mnemonic-backup').invoke('remove'); cy.matchImage(); - for (let i = 0; i < 12; i += 1) { + for (let i = 11; i >= 0; i -= 1) { cy.get('.button-mnemonic-word').eq(i).click(); } cy.get('.ae-button').click(); diff --git a/tests/e2e/specs/settings/network.cy.js b/tests/e2e/specs/settings/network.cy.js index ee246c3d6..fe5c600b5 100644 --- a/tests/e2e/specs/settings/network.cy.js +++ b/tests/e2e/specs/settings/network.cy.js @@ -8,7 +8,15 @@ describe('Settings: Network', () => { it('can be opened, changes network, adds custom network', () => { cy .viewport('iphone-5') - .visit('/settings', { login: true }) + .visit('/settings', { + login: true, + state: { + customNetworks: [{ + name: 'Testnet', + url: 'https://testnet.aeternity.io', + }], + }, + }) .get('.list-item.network') .click() .url() diff --git a/tests/e2e/specs/transfer/index.cy.js b/tests/e2e/specs/transfer/index.cy.js index 945e466ab..91e31fcd5 100644 --- a/tests/e2e/specs/transfer/index.cy.js +++ b/tests/e2e/specs/transfer/index.cy.js @@ -28,7 +28,7 @@ describe('Transfer', () => { .viewport('iphone-se2') .visit('/transfer/receive', { login: true }); cy.matchImage(); - cy.get('[data-copy-on-click="ak_mUSniVx8jR3gCTTuXBLX4htTUvWJyWwxPYoEUeEVuS9KbUpT8"]'); + cy.get('[data-copy-on-click="ak_8eAGBq1jP4dLsmnmgnSzRBxSh5SU1AVsgbCwSQcXZVwwB6c1t"]'); }); describe('redeem', () => { diff --git a/tests/e2e/specs/transfer/send-coins.cy.js b/tests/e2e/specs/transfer/send-coins.cy.js index 52c310d82..9d635137b 100644 --- a/tests/e2e/specs/transfer/send-coins.cy.js +++ b/tests/e2e/specs/transfer/send-coins.cy.js @@ -1,6 +1,6 @@ describe('Transfer: Send coins', () => { - const account1 = 'ak_mUSniVx8jR3gCTTuXBLX4htTUvWJyWwxPYoEUeEVuS9KbUpT8'; - const account2 = 'ak_22kbscYf1TbjcxXaZYCgFxbT6pASb9guJC8n7SviSvMC1cg53m'; + const account1 = 'ak_8eAGBq1jP4dLsmnmgnSzRBxSh5SU1AVsgbCwSQcXZVwwB6c1t'; + const account2 = 'ak_DNRWW4KcJyHed5b8fNizFkVb6zqykC6eFQokWgsBJLLyKdaiC'; it('sends coins', () => { const testAmount = '0.0001'; diff --git a/tests/e2e/specs/transfer/transaction-history.cy.js b/tests/e2e/specs/transfer/transaction-history.cy.js index 5c6b0dadb..134b601aa 100644 --- a/tests/e2e/specs/transfer/transaction-history.cy.js +++ b/tests/e2e/specs/transfer/transaction-history.cy.js @@ -6,7 +6,7 @@ describe('Transfer: Transaction history', () => { .get('.list-item') .contains('Transactions') .click() - .get('.list-item-transaction:not(.pending)', { timeout: 80000 }) + .get('.list-item-transaction:not(.pending)') .should('length', 15) .get('.list-item-transaction:last') .click() diff --git a/tests/e2e/support/commands.js b/tests/e2e/support/commands.js index afb8cca62..5139d6dad 100644 --- a/tests/e2e/support/commands.js +++ b/tests/e2e/support/commands.js @@ -35,13 +35,12 @@ Cypress.Commands.overwrite('visit', (originalFn, url, { contentWindow.localStorage.vuex = login || state ? JSON.stringify(Cypress._.merge( login && { migrations: Object.fromEntries(Cypress._.times(6, (i) => [i, true])), - sdkUrl: 'https://testnet.aeternity.io', accounts: { list: [{ - address: 'ak_mUSniVx8jR3gCTTuXBLX4htTUvWJyWwxPYoEUeEVuS9KbUpT8', + address: 'ak_8eAGBq1jP4dLsmnmgnSzRBxSh5SU1AVsgbCwSQcXZVwwB6c1t', source: { type: 'hd-wallet', idx: 0 }, }, { - address: 'ak_22kbscYf1TbjcxXaZYCgFxbT6pASb9guJC8n7SviSvMC1cg53m', + address: 'ak_DNRWW4KcJyHed5b8fNizFkVb6zqykC6eFQokWgsBJLLyKdaiC', source: { type: 'hd-wallet', idx: 1 }, }], hdWallet: { @@ -49,15 +48,15 @@ Cypress.Commands.overwrite('visit', (originalFn, url, { privateKey: { type: 'Uint8Array', data: [ - 133, 221, 179, 85, 188, 4, 39, 75, 56, 154, 162, 199, 27, 149, 97, 231, - 20, 88, 102, 204, 181, 38, 18, 85, 206, 120, 73, 240, 71, 134, 92, 235, + 68, 182, 66, 150, 5, 164, 0, 122, 49, 168, 211, 214, 215, 21, 209, 252, + 2, 87, 156, 34, 80, 47, 210, 39, 41, 57, 114, 132, 76, 133, 95, 152, ], }, chainCode: { type: 'Uint8Array', data: [ - 117, 7, 32, 197, 56, 211, 83, 3, 37, 112, 22, 232, 37, 26, 143, 108, - 175, 226, 168, 2, 187, 0, 150, 207, 159, 93, 31, 14, 56, 44, 74, 181, + 239, 237, 223, 34, 108, 6, 11, 247, 234, 38, 22, 33, 129, 121, 252, 96, + 45, 95, 234, 210, 221, 187, 26, 114, 144, 126, 68, 68, 154, 133, 75, 225, ], }, }, diff --git a/tests/e2e/utils.js b/tests/e2e/utils.js index 1c28f2757..c8e2ff103 100644 --- a/tests/e2e/utils.js +++ b/tests/e2e/utils.js @@ -3,8 +3,8 @@ import { AeSdk, Node, MemoryAccount } from '@aeternity/aepp-sdk-next'; // eslint-disable-next-line import/prefer-default-export export const aeSdk = new AeSdk({ nodes: [{ - instance: new Node('https://testnet.aeternity.io'), + instance: new Node('http://localhost:3013'), name: 'testnet', }], - accounts: [new MemoryAccount('ac64effca070cafbad567315f15f4839545fb606d7346772c1f74fdfb1d5fe89220f99e54be32b9cb83537389cdc8dd695e5f184f0c5a9f4bcd3f2f10e0fe3ab')], + accounts: [new MemoryAccount('9ebd7beda0c79af72a42ece3821a56eff16359b6df376cf049aee995565f022f840c974b97164776454ba119d84edc4d6058a8dec92b6edc578ab2d30b4c4200')], });