From 40a005d30295ff89a7c8f11b87a05a62d850e041 Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 02:03:40 +0000 Subject: [PATCH 01/26] Introduce bun testing --- .github/workflows/build.yml | 3 + .github/workflows/test_server.yml | 36 ++++++ test/.gitignore | 175 ++++++++++++++++++++++++++++++ test/README.md | 15 +++ test/bun.lockb | Bin 0 -> 1694 bytes test/data/basic.ts | 7 ++ test/index.ts | 1 + test/package.json | 11 ++ test/src/http_fetch.ts | 4 + test/src/test_implementation.ts | 14 +++ test/tests/server.spec.ts | 50 +++++++++ test/tests/worker.spec.ts | 40 +++++++ test/tsconfig.json | 22 ++++ worker/package.json | 2 +- 14 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test_server.yml create mode 100644 test/.gitignore create mode 100644 test/README.md create mode 100755 test/bun.lockb create mode 100644 test/data/basic.ts create mode 100644 test/index.ts create mode 100644 test/package.json create mode 100644 test/src/http_fetch.ts create mode 100644 test/src/test_implementation.ts create mode 100644 test/tests/server.spec.ts create mode 100644 test/tests/worker.spec.ts create mode 100644 test/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d143f0..ccf4b06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,12 @@ env: CARGO_TERM_COLOR: always jobs: + test_server: + uses: ./.github/workflows/test_server.yml build: name: Build ENState 🚀 runs-on: ubuntu-latest + needs: [test_server] steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml new file mode 100644 index 0000000..0abbc83 --- /dev/null +++ b/.github/workflows/test_server.yml @@ -0,0 +1,36 @@ +on: + workflow_call: + +jobs: + test: + name: Test ENState 🚀 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - run: | + rustup set auto-self-update disable + rustup toolchain install stable --profile minimal + + - uses: Swatinem/rust-cache@v2 + + - run: cargo build --release + working-directory: server + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/v3xlabs/enstate + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=edge + type=sha + + - name: Test + run: cd test && bun test tests/server.spec.ts diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..468f82a --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..28a0743 --- /dev/null +++ b/test/README.md @@ -0,0 +1,15 @@ +# enstate-coverage + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.0.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/test/bun.lockb b/test/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..447baa4ae28e880eb85db27f22ddaff719c83612 GIT binary patch literal 1694 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_lPFo!L{L zA36S`VRyif`@iy7e|=d$$xZZXOnL&7?@r10v3zVmML@s|p%^&O=msdi9Hs!w7hqs$ zkOHzefi#fKzy+k|#72o^3YCgfzezvaA@o{lzG{Pxc57-9Bh&sG97(`jQluFfAm$<)%_3E( zy3(aB>ZAXan*1B#g?bN~&$_dxht2uRb8HID>4z7^?yUxE(R zEs4IdQ)JbLg@5=`SI*sf@6tqmp3cwFS$iEg)wGKq;0oeo6R{&MN1gYpfME;tt06Y8g6&BK zMQKT@ZgM`*(8ToAlFEYA;^d;tf)Y?M3+82%loS+O>FXDzre_wH6jkcw735|W>*W`v v>%-OP>mt~?hQI(bG}9|duY$N9;$l3;AS?x%gW%|z>KOwe&>XN+2O$9fl?*Bq literal 0 HcmV?d00001 diff --git a/test/data/basic.ts b/test/data/basic.ts new file mode 100644 index 0000000..f3c51a2 --- /dev/null +++ b/test/data/basic.ts @@ -0,0 +1,7 @@ +export const dataset_names_basic: [string, string, Partial<{ address: string }>][] = [ + ["ETHRegistry_1", "luc.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], + ["ETHRegistry_2", "nick.eth", { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" }], + ["DNSRegistry", "antony.sh", { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" }], + ["CCIP Offchain RS", "luc.willbreak.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], + ["CCIP Coinbase", "lucemans.cb.id", { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" }], +]; \ No newline at end of file diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..2a5e4b8 --- /dev/null +++ b/test/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); diff --git a/test/package.json b/test/package.json new file mode 100644 index 0000000..3c77e07 --- /dev/null +++ b/test/package.json @@ -0,0 +1,11 @@ +{ + "name": "enstate-tests", + "module": "index.ts", + "type": "module", + "devDependencies": { + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/test/src/http_fetch.ts b/test/src/http_fetch.ts new file mode 100644 index 0000000..0d0dfb2 --- /dev/null +++ b/test/src/http_fetch.ts @@ -0,0 +1,4 @@ +export const http_fetch = (url: string) => async (input: string) => { + let result = await fetch(url + input); + return await result.json(); +}; diff --git a/test/src/test_implementation.ts b/test/src/test_implementation.ts new file mode 100644 index 0000000..f2d91c0 --- /dev/null +++ b/test/src/test_implementation.ts @@ -0,0 +1,14 @@ +import { describe, expect, test } from "bun:test"; +import { dataset_names_basic } from "../data/basic"; + +export const test_implementation = ][], DataType extends {}>(function_name: string, x: (input: string) => Promise>, dataset: DataSet) => { + describe("t/" + function_name, () => { + for (const [label, input, partial] of dataset) { + test(label + ` (${input})`, async () => { + let output = await x(input); + + expect(output).toMatchObject(partial); + }); + } + }); +}; \ No newline at end of file diff --git a/test/tests/server.spec.ts b/test/tests/server.spec.ts new file mode 100644 index 0000000..5082e9c --- /dev/null +++ b/test/tests/server.spec.ts @@ -0,0 +1,50 @@ +import { expect, test, describe, beforeAll, afterAll } from "bun:test"; +import { test_implementation } from "../src/test_implementation"; +import { Subprocess } from "bun"; +import { http_fetch } from "../src/http_fetch"; +import { dataset_names_basic } from "../data/basic"; + +const TEST_RELEASE = true; + +let server: Subprocess | undefined = undefined; + +beforeAll(async () => { + console.log("Building server..."); + + await new Promise((resolve) => { + Bun.spawn(["cargo", "build", TEST_RELEASE ? "--release" : ''], { + cwd: "../server", onExit(proc, exitCode, signalCode, error) { + resolve(); + } + }); + }); + + console.log("Build finished!"); + + server = Bun.spawn([`../server/target/${TEST_RELEASE ? 'release' : 'debug'}/enstate`], { cwd: "../server" }); + + console.log('Waiting for server to start...'); + + let attempts = 0; + while (attempts < 10) { + try { + console.log("Attempting heartbeat..."); + await fetch("http://0.0.0.0:3000/"); + console.log("Heartbeat succes!"); + break; + } catch (e) { + console.log("Waiting another 1s for heartbeat..."); + attempts++; + await new Promise((resolve) => setTimeout(resolve, 1000)); + continue; + } + } + + console.log("Ready to start testing"); +}); + +afterAll(async () => { + server?.kill(); +}); + +test_implementation("enstate", http_fetch("http://0.0.0.0:3000/n/"), dataset_names_basic); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts new file mode 100644 index 0000000..b805336 --- /dev/null +++ b/test/tests/worker.spec.ts @@ -0,0 +1,40 @@ +import { expect, test, describe, beforeAll, afterAll } from "bun:test"; +import { test_implementation } from "../src/test_implementation"; +import { Subprocess } from "bun"; +import { dataset_names_basic } from "../data/basic"; +import { http_fetch } from "../src/http_fetch"; + +let server: Subprocess | undefined = undefined; + +beforeAll(async () => { + console.log("Building worker..."); + + server = Bun.spawn(['pnpm', 'dev'], { cwd: "../worker" }); + + console.log('Waiting for server to start...'); + + let attempts = 0; + while (attempts < 30) { + try { + console.log("Attempting heartbeat..."); + await fetch("http://0.0.0.0:3001/"); + console.log("Heartbeat succes!"); + break; + } catch (e) { + console.log("Waiting another 1s for heartbeat..."); + attempts++; + await new Promise((resolve) => setTimeout(resolve, 1000)); + continue; + } + } + + console.log("Ready to start testing"); +}); + +afterAll(async () => { + server?.kill(); + + await server?.exited; +}); + +test_implementation("enstate", http_fetch("http://0.0.0.0:3001/n/"), dataset_names_basic); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..7556e1d --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/worker/package.json b/worker/package.json index d03556e..74f33ab 100644 --- a/worker/package.json +++ b/worker/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "deploy": "wrangler deploy", - "dev": "wrangler dev" + "dev": "wrangler dev --port 3001" }, "devDependencies": { "wrangler": "^3.19.0" From fff3d6bfa8380e5817c6531fd4b107cdeec531f2 Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 02:09:42 +0000 Subject: [PATCH 02/26] x --- .github/workflows/test_server.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index 0abbc83..9465557 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -19,18 +19,9 @@ jobs: - run: cargo build --release working-directory: server - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/v3xlabs/enstate - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=edge - type=sha + - uses: oven-sh/setup-bun@v1 + + - run: bun install - name: Test run: cd test && bun test tests/server.spec.ts From 60ced4b4954ac146d824428fd94652e5a076af90 Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 02:16:37 +0000 Subject: [PATCH 03/26] x --- .github/workflows/test_server.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index 9465557..1db6d55 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -22,6 +22,8 @@ jobs: - uses: oven-sh/setup-bun@v1 - run: bun install + working-directory: test - name: Test - run: cd test && bun test tests/server.spec.ts + run: bun test tests/server.spec.ts + working-directory: test From 8306aac3121ce8215f189d98882552496f07b58b Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 02:19:01 +0000 Subject: [PATCH 04/26] x --- .github/workflows/build.yml | 9 +++++++-- .github/workflows/build_server.yaml | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build_server.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ccf4b06..65a36e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,15 +1,20 @@ name: Build and Deploy on: push: - tags: - - "v*.*.*" + # tags: + # - "v*.*.*" + branches: + - "ci/integration-tests" env: CARGO_TERM_COLOR: always jobs: + build_server: + uses: ./.github/workflows/build_server.yml test_server: uses: ./.github/workflows/test_server.yml + needs: [build_server] build: name: Build ENState 🚀 runs-on: ubuntu-latest diff --git a/.github/workflows/build_server.yaml b/.github/workflows/build_server.yaml new file mode 100644 index 0000000..094111f --- /dev/null +++ b/.github/workflows/build_server.yaml @@ -0,0 +1,20 @@ +on: + workflow_call: + +jobs: + build: + name: Build ENState 🚀 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - run: | + rustup set auto-self-update disable + rustup toolchain install stable --profile minimal + + - uses: Swatinem/rust-cache@v2 + + - run: cargo build --release + working-directory: server From e710ffc32b124ada507b98c92505efa9364cef66 Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 02:19:39 +0000 Subject: [PATCH 05/26] x --- .github/workflows/{build_server.yaml => build_server.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build_server.yaml => build_server.yml} (100%) diff --git a/.github/workflows/build_server.yaml b/.github/workflows/build_server.yml similarity index 100% rename from .github/workflows/build_server.yaml rename to .github/workflows/build_server.yml From d4289d5c7c5508a38812ded5d0d5008d709fc5ca Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 03:17:37 +0000 Subject: [PATCH 06/26] x --- .github/workflows/test_server.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index 1db6d55..dab1127 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -27,3 +27,5 @@ jobs: - name: Test run: bun test tests/server.spec.ts working-directory: test + env: + RPC_URL: https://rpc.ankr.com/eth From 474dd690e45049673235ea5d0a60ce041791c39c Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 03:53:02 +0000 Subject: [PATCH 07/26] x --- server/src/database/mod.rs | 2 +- shared/src/cache/mod.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/server/src/database/mod.rs b/server/src/database/mod.rs index 1de0215..91fd998 100644 --- a/server/src/database/mod.rs +++ b/server/src/database/mod.rs @@ -4,7 +4,7 @@ use anyhow::Result; use redis::aio::ConnectionManager; pub async fn setup() -> Result { - let redis = redis::Client::open(env::var("REDIS_URL").expect("REDIS_URL should've been set"))?; + let redis = redis::Client::open(env::var("REDIS_URL")?)?; Ok(ConnectionManager::new(redis).await?) } diff --git a/shared/src/cache/mod.rs b/shared/src/cache/mod.rs index c506bec..3674352 100644 --- a/shared/src/cache/mod.rs +++ b/shared/src/cache/mod.rs @@ -13,3 +13,17 @@ pub trait CacheLayer: Send + Sync { async fn get(&self, key: &str) -> Result; async fn set(&self, key: &str, value: &str, expires: u32) -> Result<(), CacheError>; } + +pub struct PassthroughCacheLayer {} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl CacheLayer for PassthroughCacheLayer { + async fn get(&self, _key: &str) -> Result { + Err(CacheError::Other("".to_string())) + } + + async fn set(&self, _key: &str, _value: &str, _expires: u32) -> Result<(), CacheError> { + Ok(()) + } +} From 6fe0a22a2189498ae415bfe8a5ead225d45959ae Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 04:03:51 +0000 Subject: [PATCH 08/26] x --- .github/workflows/build.yml | 3 --- .github/workflows/build_server.yml | 20 -------------------- .github/workflows/test_server.yml | 1 + 3 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .github/workflows/build_server.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65a36e5..0f50592 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,11 +10,8 @@ env: CARGO_TERM_COLOR: always jobs: - build_server: - uses: ./.github/workflows/build_server.yml test_server: uses: ./.github/workflows/test_server.yml - needs: [build_server] build: name: Build ENState 🚀 runs-on: ubuntu-latest diff --git a/.github/workflows/build_server.yml b/.github/workflows/build_server.yml deleted file mode 100644 index 094111f..0000000 --- a/.github/workflows/build_server.yml +++ /dev/null @@ -1,20 +0,0 @@ -on: - workflow_call: - -jobs: - build: - name: Build ENState 🚀 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: | - rustup set auto-self-update disable - rustup toolchain install stable --profile minimal - - - uses: Swatinem/rust-cache@v2 - - - run: cargo build --release - working-directory: server diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index dab1127..607796b 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -29,3 +29,4 @@ jobs: working-directory: test env: RPC_URL: https://rpc.ankr.com/eth + OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }} From b9a590eb2cc3c59e99d005cdec9ec2203eac419a Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 11 Dec 2023 04:11:50 +0000 Subject: [PATCH 09/26] x --- .github/workflows/build.yml | 13 ++++++++++++- .github/workflows/test_server.yml | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f50592..3a5244d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,18 @@ jobs: rustup set auto-self-update disable rustup toolchain install stable --profile minimal - - uses: Swatinem/rust-cache@v2 + - name: Set up cargo cache + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + server/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- - run: cargo build --release working-directory: server diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index 607796b..3eb7d7c 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -14,7 +14,18 @@ jobs: rustup set auto-self-update disable rustup toolchain install stable --profile minimal - - uses: Swatinem/rust-cache@v2 + - name: Set up cargo cache + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + server/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- - run: cargo build --release working-directory: server From 7b5f061f191e1c222b59e871983a3a9cec14416c Mon Sep 17 00:00:00 2001 From: Luc Date: Tue, 2 Jan 2024 00:45:53 +0000 Subject: [PATCH 10/26] Update Caching in Test Server --- .github/workflows/test_server.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml index 3eb7d7c..d91b9f9 100644 --- a/.github/workflows/test_server.yml +++ b/.github/workflows/test_server.yml @@ -5,6 +5,9 @@ jobs: test: name: Test ENState 🚀 runs-on: ubuntu-latest + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" steps: - uses: actions/checkout@v3 with: @@ -14,18 +17,10 @@ jobs: rustup set auto-self-update disable rustup toolchain install stable --profile minimal - - name: Set up cargo cache - uses: actions/cache@v3 - continue-on-error: false + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - server/target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + version: "v0.7.4" - run: cargo build --release working-directory: server From 33de132269b7945f03cc6180fd2c98e131bf6f5f Mon Sep 17 00:00:00 2001 From: Luc Date: Tue, 2 Jan 2024 00:51:29 +0000 Subject: [PATCH 11/26] Bump --- .github/workflows/build.yml | 17 ++++++----------- .github/workflows/pr_check.yml | 18 ++++++------------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a5244d..79cbf43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,9 @@ jobs: name: Build ENState 🚀 runs-on: ubuntu-latest needs: [test_server] + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" steps: - uses: actions/checkout@v3 with: @@ -25,18 +28,10 @@ jobs: rustup set auto-self-update disable rustup toolchain install stable --profile minimal - - name: Set up cargo cache - uses: actions/cache@v3 - continue-on-error: false + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - server/target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + version: "v0.7.4" - run: cargo build --release working-directory: server diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index d379e1a..9cd3771 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -19,6 +19,9 @@ jobs: target: x86_64-unknown-linux-gnu - path: worker target: wasm32-unknown-unknown + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" steps: - uses: actions/checkout@v3 with: @@ -29,19 +32,10 @@ jobs: rustup toolchain install stable --profile minimal rustup target add ${{ matrix.target }} - - name: Set up cargo cache - uses: actions/cache@v3 - continue-on-error: false + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - server/target/ - worker/target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-cargo- + version: "v0.7.4" - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} From 722ee0547d6289d35524a98cd78a7b73817f1f8f Mon Sep 17 00:00:00 2001 From: Luc Date: Tue, 2 Jan 2024 01:19:28 +0000 Subject: [PATCH 12/26] Update Testing --- .github/workflows/pr_check.yml | 1 - test/README.md | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 9cd3771..1113f83 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -39,4 +39,3 @@ jobs: - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} - diff --git a/test/README.md b/test/README.md index 28a0743..4a658a8 100644 --- a/test/README.md +++ b/test/README.md @@ -1,15 +1,7 @@ # enstate-coverage -To install dependencies: - -```bash -bun install -``` - To run: ```bash -bun run index.ts +bun test ``` - -This project was created using `bun init` in bun v1.0.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. From 4c6c949128f82c417feb34b50db0e59427e3ed2c Mon Sep 17 00:00:00 2001 From: Luc Date: Tue, 2 Jan 2024 03:20:45 +0000 Subject: [PATCH 13/26] Bump --- test/data/basic.ts | 3 ++- test/index.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/data/basic.ts b/test/data/basic.ts index f3c51a2..6262655 100644 --- a/test/data/basic.ts +++ b/test/data/basic.ts @@ -1,7 +1,8 @@ export const dataset_names_basic: [string, string, Partial<{ address: string }>][] = [ ["ETHRegistry_1", "luc.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], ["ETHRegistry_2", "nick.eth", { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" }], + ["DNSRegistry", "luc.computer", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], ["DNSRegistry", "antony.sh", { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" }], ["CCIP Offchain RS", "luc.willbreak.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], ["CCIP Coinbase", "lucemans.cb.id", { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" }], -]; \ No newline at end of file +]; diff --git a/test/index.ts b/test/index.ts index 2a5e4b8..4d99dce 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1 +1 @@ -console.log("Hello via Bun!"); +console.log("Heya! You probably ment to run `bun test`"); From ff423cadce9f082662ed77eb1736e194461055a4 Mon Sep 17 00:00:00 2001 From: Luc Date: Tue, 2 Jan 2024 03:24:25 +0000 Subject: [PATCH 14/26] Bump --- .github/workflows/build.yml | 7 +++---- .github/workflows/pr_check.yml | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79cbf43..480d72f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,9 @@ name: Build and Deploy on: push: - # tags: - # - "v*.*.*" - branches: - - "ci/integration-tests" + tags: + - "v*.*.*" + workflow_call: env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 1113f83..db60fab 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -1,4 +1,4 @@ -name: Build and Deploy +name: PR Check on: pull_request: branches: @@ -39,3 +39,6 @@ jobs: - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} + test_server: + uses: ./.github/workflows/test_server.yml + needs: [check] From 27ba77a7c084bdc717b63f0691412ad1e554147f Mon Sep 17 00:00:00 2001 From: Luc van Kampen Date: Tue, 2 Jan 2024 03:36:32 +0000 Subject: [PATCH 15/26] Update test/src/test_implementation.ts --- test/src/test_implementation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test_implementation.ts b/test/src/test_implementation.ts index f2d91c0..fd66491 100644 --- a/test/src/test_implementation.ts +++ b/test/src/test_implementation.ts @@ -11,4 +11,4 @@ export const test_implementation = Date: Fri, 5 Jan 2024 12:20:54 +0100 Subject: [PATCH 16/26] Restructure datasets & introduce address and universal endpoint tests --- server/src/state.rs | 18 +++++++++++--- test/data/basic.ts | 43 +++++++++++++++++++++++++++------ test/src/test_implementation.ts | 12 ++++----- test/tests/server.spec.ts | 20 +++++---------- test/tests/worker.spec.ts | 10 +++++--- worker/package.json | 2 +- 6 files changed, 69 insertions(+), 36 deletions(-) diff --git a/server/src/state.rs b/server/src/state.rs index b5c45ee..2ef96d2 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,3 +1,4 @@ +use enstate_shared::cache::{CacheLayer, PassthroughCacheLayer}; use std::env; use std::sync::Arc; @@ -6,7 +7,7 @@ use enstate_shared::models::{ multicoin::cointype::{coins::CoinType, Coins}, records::Records, }; -use tracing::info; +use tracing::{info, warn}; use crate::provider::RoundRobin; use crate::{cache, database}; @@ -43,9 +44,18 @@ impl AppState { info!("Connecting to Redis..."); - let redis = database::setup().await.expect("Redis connection failed"); + let cache = database::setup().await.map_or_else( + |_| { + warn!("failed to connect to redis, using no cache"); - info!("Connected to Redis"); + Box::new(PassthroughCacheLayer {}) as Box + }, + |redis| { + info!("Connected to Redis"); + + Box::new(cache::Redis::new(redis)) as Box + }, + ); let provider = RoundRobin::new(rpc_urls); @@ -54,7 +64,7 @@ impl AppState { Self { service: ProfileService { - cache: Box::new(cache::Redis::new(redis)), + cache, rpc: Box::new(provider), opensea_api_key, profile_records: Arc::from(profile_records), diff --git a/test/data/basic.ts b/test/data/basic.ts index 6262655..268d14a 100644 --- a/test/data/basic.ts +++ b/test/data/basic.ts @@ -1,8 +1,37 @@ -export const dataset_names_basic: [string, string, Partial<{ address: string }>][] = [ - ["ETHRegistry_1", "luc.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], - ["ETHRegistry_2", "nick.eth", { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" }], - ["DNSRegistry", "luc.computer", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], - ["DNSRegistry", "antony.sh", { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" }], - ["CCIP Offchain RS", "luc.willbreak.eth", { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" }], - ["CCIP Coinbase", "lucemans.cb.id", { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" }], +export type DatasetEntry = { + label: string, + arg: string, + expected: T +} + +export type Dataset = DatasetEntry[]; + +export const dataset_name_basic: Dataset<{ address: string }> = [ + { label: "ETHRegistry_1", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "ETHRegistry_2", arg: "nick.eth", expected: { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" } }, + { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "DNSRegistry", arg: "antony.sh", expected: { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" } }, + { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "CCIP Coinbase", arg: "lucemans.cb.id", expected: { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" } }, ]; + +export const dataset_address_basic: Dataset<{ name: string }> = [ + { label: "ETHRegistry_1", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.eth" } }, + { label: "ETHRegistry_2", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, + // TODO: find another dns primary name address + // { label: "DNSRegistry", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.computer" } }, + { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, + // TODO: find 2 ccip primary name addresses + // { label: "CCIP Offchain RS", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.willbreak.eth" } }, + // { label: "CCIP Coinbase", arg: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b", expected: { name: "lucemans.cb.id" } }, +]; + +export const dataset_universal_basic: Dataset<{ address: string} | {name: string }> = [ + { label: "ETHRegistry_1", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "ETHRegistry_2", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, + { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, + { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + // TODO: refer to above todo + // { label: "CCIP Coinbase", arg: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b", expected: { name: "lucemans.cb.id" } }, +] \ No newline at end of file diff --git a/test/src/test_implementation.ts b/test/src/test_implementation.ts index fd66491..d571464 100644 --- a/test/src/test_implementation.ts +++ b/test/src/test_implementation.ts @@ -1,13 +1,13 @@ import { describe, expect, test } from "bun:test"; -import { dataset_names_basic } from "../data/basic"; +import { Dataset } from "../data/basic"; -export const test_implementation = ][], DataType extends {}>(function_name: string, x: (input: string) => Promise>, dataset: DataSet) => { +export const test_implementation = , DataType extends {}>(function_name: string, fn: (input: string) => Promise>, dataset: DataSet) => { describe("t/" + function_name, () => { - for (const [label, input, partial] of dataset) { - test(label + ` (${input})`, async () => { - let output = await x(input); + for (const { label, arg, expected } of dataset) { + test(label + ` (${arg})`, async () => { + let output = await fn(arg); - expect(output).toMatchObject(partial); + expect(output).toMatchObject(expected); }); } }); diff --git a/test/tests/server.spec.ts b/test/tests/server.spec.ts index 5082e9c..180a5e6 100644 --- a/test/tests/server.spec.ts +++ b/test/tests/server.spec.ts @@ -2,26 +2,16 @@ import { expect, test, describe, beforeAll, afterAll } from "bun:test"; import { test_implementation } from "../src/test_implementation"; import { Subprocess } from "bun"; import { http_fetch } from "../src/http_fetch"; -import { dataset_names_basic } from "../data/basic"; +import { dataset_address_basic, dataset_name_basic, dataset_universal_basic } from "../data/basic"; const TEST_RELEASE = true; let server: Subprocess | undefined = undefined; beforeAll(async () => { - console.log("Building server..."); + console.log("Building and running server..."); - await new Promise((resolve) => { - Bun.spawn(["cargo", "build", TEST_RELEASE ? "--release" : ''], { - cwd: "../server", onExit(proc, exitCode, signalCode, error) { - resolve(); - } - }); - }); - - console.log("Build finished!"); - - server = Bun.spawn([`../server/target/${TEST_RELEASE ? 'release' : 'debug'}/enstate`], { cwd: "../server" }); + server = Bun.spawn(["cargo", "run", TEST_RELEASE ? "--release" : ''], { cwd: "../server" }); console.log('Waiting for server to start...'); @@ -47,4 +37,6 @@ afterAll(async () => { server?.kill(); }); -test_implementation("enstate", http_fetch("http://0.0.0.0:3000/n/"), dataset_names_basic); +test_implementation("server/name", http_fetch("http://0.0.0.0:3000/n/"), dataset_name_basic); +test_implementation("server/address", http_fetch("http://0.0.0.0:3000/a/"), dataset_address_basic); +test_implementation("server/universal", http_fetch("http://0.0.0.0:3000/u/"), dataset_universal_basic); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index b805336..526fa4f 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -1,7 +1,7 @@ import { expect, test, describe, beforeAll, afterAll } from "bun:test"; import { test_implementation } from "../src/test_implementation"; import { Subprocess } from "bun"; -import { dataset_names_basic } from "../data/basic"; +import { dataset_address_basic, dataset_name_basic, dataset_universal_basic } from "../data/basic"; import { http_fetch } from "../src/http_fetch"; let server: Subprocess | undefined = undefined; @@ -9,7 +9,7 @@ let server: Subprocess | undefined = undefined; beforeAll(async () => { console.log("Building worker..."); - server = Bun.spawn(['pnpm', 'dev'], { cwd: "../worker" }); + server = Bun.spawn(['pnpm', 'dev', '--port', '3000'], { cwd: "../worker" }); console.log('Waiting for server to start...'); @@ -17,7 +17,7 @@ beforeAll(async () => { while (attempts < 30) { try { console.log("Attempting heartbeat..."); - await fetch("http://0.0.0.0:3001/"); + await fetch("http://0.0.0.0:3000/"); console.log("Heartbeat succes!"); break; } catch (e) { @@ -37,4 +37,6 @@ afterAll(async () => { await server?.exited; }); -test_implementation("enstate", http_fetch("http://0.0.0.0:3001/n/"), dataset_names_basic); +test_implementation("worker/name", http_fetch("http://0.0.0.0:3001/n/"), dataset_name_basic); +test_implementation("worker/address", http_fetch("http://0.0.0.0:3001/n/"), dataset_address_basic); +test_implementation("worker/universal", http_fetch("http://0.0.0.0:3001/n/"), dataset_universal_basic); diff --git a/worker/package.json b/worker/package.json index 74f33ab..d03556e 100644 --- a/worker/package.json +++ b/worker/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "deploy": "wrangler deploy", - "dev": "wrangler dev --port 3001" + "dev": "wrangler dev" }, "devDependencies": { "wrangler": "^3.19.0" From 6b6937266882ee80a6ec52c11ebb0aca658e7935 Mon Sep 17 00:00:00 2001 From: Antony1060 Date: Fri, 5 Jan 2024 13:45:25 +0100 Subject: [PATCH 17/26] Introduce bulk endpoint testing --- test/bun.lockb | Bin 1694 -> 7021 bytes test/data/bulk.ts | 22 ++++++++++++++++++++++ test/data/index.ts | 10 ++++++++++ test/data/{basic.ts => single.ts} | 26 ++++++++++---------------- test/package.json | 4 ++++ test/src/test_implementation.ts | 2 +- test/tests/server.spec.ts | 27 +++++++++++++++++++++------ test/tests/worker.spec.ts | 8 ++++---- 8 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 test/data/bulk.ts create mode 100644 test/data/index.ts rename test/data/{basic.ts => single.ts} (60%) diff --git a/test/bun.lockb b/test/bun.lockb index 447baa4ae28e880eb85db27f22ddaff719c83612..7f24e54a8407b5196c657897a514d9edcb53892a 100755 GIT binary patch literal 7021 zcmeHLdtA(E7yp%U>oQ_jVo5Me5$|B!82aX_ zb;wY(9&{6Wz*GHa8k61R#yuCr2tv2ioj2_9W@b(Nogc?Uj7rHrImWPh?}+Wj2fQrZ zTvm5pIpI#p=H#<|!a@)0cT8PdY##G4y~92l^!}ju zfjpszAz+^*>_099%y13+QJ!fFVfhGOc4=(u%ZrR|Bk2%ITuo@&ep|N- zp(!2NF@^bF-#*{4<-58wKIK+Po3rD{3EAVLM|j878--d;Za3omk+j_pUoH8W9by%~ zAmjG94Ko()Iny-`bYoon9w2jQyEa@ndt9NyCCK=Q}J0`W;(Q4H-m}9DnYN9D#`NdRa=Y$j?Zzpny@~N*`R+~ULV47TQSn^ zMdSMA0EF#DXN`$gO51slTw3a~m=__89yvPwc3muE>&!D{X5LHk)@_L4)-p5M&kd&A zR2EEX=sB=9;BxzC>({&88WmJC&xBs>58rJyu4dk%9h(q3czPvclOM&65_K+pYvPv| zqf__9u()`+Hew&+ ze)iLiWT~^4Np!$s>w8v~8_NRRJw((Ei|R>3$`XhFM-b8L;)UQ_Z!~am{33YTv+d4t z9}?akZvU!i^rHusG{3^C$L!6nyiAunR?9M(k;O&1cPrLh>dgJc z>Gl4!llcSetztEBQEtOT(~l@Q{ld3!x>s6C2g`J)a}>SXn(&vGoqN!R)>?0UQ{+&5 z)wg%q?cb(s_Ht}AFwSh3lXQJrU#{EnFxSMpgLZ`h5cUJysnkUCj?G?^JAR18o|yQx zk~H^c^qN%ioDb!cmVaedyNt5>8N3~Z-5+6)(4MO zL@9HQ^4!o}1{2Nu^!tc((}LH#FZF)DN@DuzsDp2G$PXc<0a zrp319odbq>9PP5>dBVf955_sTxWz47VdxD&*j|(mG12Dd%(D0BGvB0ATw_lj@ZXr8 z?X&ezLWNN1wP)QfyWC}q5C7S7+%eA1_$#}*&$&|ZgkD)Ql{bLmn3B~6gY5%P01(E- zc@<&PzR8bW>r}DMN7m7-eEc8DxzqDs8U(xxo!T&q5mY=b+A6jDqDSI=b9TIG{m4=G zJR^m+BjpJdNlz31)ZMbrP#mR!3wL8R(bCAF(Q}1H|2oVY?3|WR_~Rn|c@{VJTO{_& zuDyKDFFDt2$pn+B*9_{d_L#i!wOnmFY_dVtuk7#tI=FS|8F6K3o(66gH7eNKC(I&i zbnMI*Q!g(Tm-&RT?!=rQ*HP%5+MxG`<3@Syv9+d_&J*wXFe+XKtgbRm=vk0nek5+?gaO6wI4LUf3!?_>Ym+`N~>KbR!uI^IZpFmcQ$Lq@W~5=Q(k%$EXkNv zmS>z@v;94tpS9&cdFk3N2J-7ZVSYNl2Cw2qq+J{|OT)&m&Cl}y&excK4vOZt?{nn8 z%6#>}|CI-lRAr3}71wj3q&yZWqGW*~AuL=|VNI9v`OXY3n?aEa`C@@JV<;mqgcORz z0_9h;@q?0(Z&2PWQNbB?Q+i9!Np8z^2B}u$QCyABmKx8)JET5WD9EJ z2alDhYzaaa0U$l6SzLR6xdZsnUhM-;a~~qR06!AfRg83I+p*XzHbLOZ53am)(hUie z@hKTWn1c-19*Ju+y6Q_fu1?|Vkgm>bdlrYo;u5%Ch3iEy!vQ_;uVbbP4OfIzGhjgu z%)q`QaZO1z!zBn@^}-L}-vWM4J|uRrDbCpwkcI(OBMdN{e*rbt=OVUR?${Se#)WhLj_57TOnzObW(8C>dl1kMMBwJ zEt_azHNdtos&I+Iv|zq~6!DqCfwDlf0|{R$i%{)G(Pjp#0zoYO#G$FhHAR~I@n2|d zat}&HfPq+>*0;1BkZ)0Is*ZX9Q3Uod^g+}fBkGg^GU~R{dV3_QOlHairbTsA W4g#E}4%3ch3x9y4OmY9b-@gGk*>nv6 delta 322 zcmaEBHjj6Lo~F~z?5WR>9RJaD;w3Ls4k4FCQ^ z0Ei9N&H`dFFmSL;UMHx; diff --git a/test/data/bulk.ts b/test/data/bulk.ts new file mode 100644 index 0000000..57e6526 --- /dev/null +++ b/test/data/bulk.ts @@ -0,0 +1,22 @@ +import { Dataset } from "."; +import qs from "qs"; + +export const dataset_name_bulk: Dataset<{response: {address: string}[], response_length: number }> = [{ + label: "ETHRegistry", arg: qs.stringify({names: ["luc.eth", "nick.eth"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"}], response_length: 2 }},{ + label: "ETHRegistry (extra)", arg: qs.stringify({names: ["luc.eth", "nick.eth", "nick.eth"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"}], response_length: 2 }},{ + label: "DNSRegistry", arg: qs.stringify({names: ["luc.computer", "antony.sh"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190"}], response_length: 2 }},{ + label: "CCIP", arg: qs.stringify({names: ["luc.willbreak.eth", "lucemans.cb.id"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b"}], response_length: 2 } +}] + +export const dataset_address_bulk: Dataset<{response: {name: string}[], response_length: number }> = [{ + label: "ETHRegistry", arg: qs.stringify({addresses: ["0x225f137127d9067788314bc7fcc1f36746a3c3B5", "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"] }, { encode: false}), expected: {response: [{name: "luc.eth"}, {name: "nick.eth"}], response_length: 2 }}, + {label: "ETHRegistry (extra)", arg: qs.stringify({addresses: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "0x2B5c7025998f88550Ef2fEce8bf87935F542c190"] }, { encode: false}), expected: {response: [{name: "antony.sh"}], response_length: 1 }}, + {label: "DNSRegistry", arg: qs.stringify({addresses: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190"] }, { encode: false}), expected: {response: [{name: "antony.sh"}], response_length: 1 }}, + // {label: "CCIP", arg: qs.stringify({names: ["luc.willbreak.eth", "lucemans.cb.id"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}], response_length: 2 }} +] + +export const dataset_universal_bulk: Dataset<{response: ({address: string} | {name: string})[], response_length: number }> = [{ + label: "ETHRegistry", arg: qs.stringify({queries: ["luc.eth", "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {name: "nick.eth"}], response_length: 2 }},{ + label: "DNSRegistry", arg: qs.stringify({queries: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "antony.sh"] }, { encode: false}), expected: {response: [{name: "antony.sh"}, {address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190"}], response_length: 2 }},{ + label: "Mixed", arg: qs.stringify({queries: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "luc.eth", "luc.willbreak.eth"] }, { encode: false}), expected: {response: [{name: "antony.sh"},{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}], response_length: 3 } +}] \ No newline at end of file diff --git a/test/data/index.ts b/test/data/index.ts new file mode 100644 index 0000000..00b30c0 --- /dev/null +++ b/test/data/index.ts @@ -0,0 +1,10 @@ +export * from "./single" +export * from "./bulk" + +export type DatasetEntry = { + label: string, + arg: string, + expected: T +} + +export type Dataset = DatasetEntry[]; diff --git a/test/data/basic.ts b/test/data/single.ts similarity index 60% rename from test/data/basic.ts rename to test/data/single.ts index 268d14a..9d4bcd9 100644 --- a/test/data/basic.ts +++ b/test/data/single.ts @@ -1,23 +1,17 @@ -export type DatasetEntry = { - label: string, - arg: string, - expected: T -} +import { Dataset } from "."; -export type Dataset = DatasetEntry[]; - -export const dataset_name_basic: Dataset<{ address: string }> = [ - { label: "ETHRegistry_1", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "ETHRegistry_2", arg: "nick.eth", expected: { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" } }, +export const dataset_name_single: Dataset<{ address: string }> = [ + { label: "ETHRegistry", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "ETHRegistry", arg: "nick.eth", expected: { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" } }, { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, { label: "DNSRegistry", arg: "antony.sh", expected: { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" } }, { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, { label: "CCIP Coinbase", arg: "lucemans.cb.id", expected: { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" } }, ]; -export const dataset_address_basic: Dataset<{ name: string }> = [ - { label: "ETHRegistry_1", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.eth" } }, - { label: "ETHRegistry_2", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, +export const dataset_address_single: Dataset<{ name: string }> = [ + { label: "ETHRegistry", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.eth" } }, + { label: "ETHRegistry", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, // TODO: find another dns primary name address // { label: "DNSRegistry", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.computer" } }, { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, @@ -26,9 +20,9 @@ export const dataset_address_basic: Dataset<{ name: string }> = [ // { label: "CCIP Coinbase", arg: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b", expected: { name: "lucemans.cb.id" } }, ]; -export const dataset_universal_basic: Dataset<{ address: string} | {name: string }> = [ - { label: "ETHRegistry_1", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "ETHRegistry_2", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, +export const dataset_universal_single: Dataset<{ address: string} | {name: string }> = [ + { label: "ETHRegistry", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, + { label: "ETHRegistry", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, diff --git a/test/package.json b/test/package.json index 3c77e07..3142999 100644 --- a/test/package.json +++ b/test/package.json @@ -7,5 +7,9 @@ }, "peerDependencies": { "typescript": "^5.0.0" + }, + "dependencies": { + "@types/qs": "^6.9.11", + "qs": "^6.11.2" } } \ No newline at end of file diff --git a/test/src/test_implementation.ts b/test/src/test_implementation.ts index d571464..7b7793b 100644 --- a/test/src/test_implementation.ts +++ b/test/src/test_implementation.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { Dataset } from "../data/basic"; +import { Dataset } from "../data"; export const test_implementation = , DataType extends {}>(function_name: string, fn: (input: string) => Promise>, dataset: DataSet) => { describe("t/" + function_name, () => { diff --git a/test/tests/server.spec.ts b/test/tests/server.spec.ts index 180a5e6..9142559 100644 --- a/test/tests/server.spec.ts +++ b/test/tests/server.spec.ts @@ -2,16 +2,27 @@ import { expect, test, describe, beforeAll, afterAll } from "bun:test"; import { test_implementation } from "../src/test_implementation"; import { Subprocess } from "bun"; import { http_fetch } from "../src/http_fetch"; -import { dataset_address_basic, dataset_name_basic, dataset_universal_basic } from "../data/basic"; +import { dataset_address_single, dataset_name_single, dataset_universal_single } from "../data/single"; +import { dataset_address_bulk, dataset_name_bulk, dataset_universal_bulk } from "../data"; const TEST_RELEASE = true; let server: Subprocess | undefined = undefined; beforeAll(async () => { - console.log("Building and running server..."); + console.log("Building server..."); - server = Bun.spawn(["cargo", "run", TEST_RELEASE ? "--release" : ''], { cwd: "../server" }); + await new Promise((resolve) => { + Bun.spawn(["cargo", "build", TEST_RELEASE ? "--release" : ''], { + cwd: "../server", onExit(proc, exitCode, signalCode, error) { + resolve(); + } + }); + }); + + console.log("Build finished!"); + + server = Bun.spawn([`../server/target/${TEST_RELEASE ? 'release' : 'debug'}/enstate`], { cwd: "../server" }); console.log('Waiting for server to start...'); @@ -37,6 +48,10 @@ afterAll(async () => { server?.kill(); }); -test_implementation("server/name", http_fetch("http://0.0.0.0:3000/n/"), dataset_name_basic); -test_implementation("server/address", http_fetch("http://0.0.0.0:3000/a/"), dataset_address_basic); -test_implementation("server/universal", http_fetch("http://0.0.0.0:3000/u/"), dataset_universal_basic); +test_implementation("server/name", http_fetch("http://0.0.0.0:3000/n/"), dataset_name_single); +test_implementation("server/address", http_fetch("http://0.0.0.0:3000/a/"), dataset_address_single); +test_implementation("server/universal", http_fetch("http://0.0.0.0:3000/u/"), dataset_universal_single); + +test_implementation("server/bulk/name", http_fetch("http://0.0.0.0:3000/bulk/n?"), dataset_name_bulk); +test_implementation("server/bulk/address", http_fetch("http://0.0.0.0:3000/bulk/a?"), dataset_address_bulk); +test_implementation("server/bulk/universal", http_fetch("http://0.0.0.0:3000/bulk/u?"), dataset_universal_bulk); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 526fa4f..c05dab8 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -1,7 +1,7 @@ import { expect, test, describe, beforeAll, afterAll } from "bun:test"; import { test_implementation } from "../src/test_implementation"; import { Subprocess } from "bun"; -import { dataset_address_basic, dataset_name_basic, dataset_universal_basic } from "../data/basic"; +import { dataset_address_single, dataset_name_single, dataset_universal_single } from "../data/single"; import { http_fetch } from "../src/http_fetch"; let server: Subprocess | undefined = undefined; @@ -37,6 +37,6 @@ afterAll(async () => { await server?.exited; }); -test_implementation("worker/name", http_fetch("http://0.0.0.0:3001/n/"), dataset_name_basic); -test_implementation("worker/address", http_fetch("http://0.0.0.0:3001/n/"), dataset_address_basic); -test_implementation("worker/universal", http_fetch("http://0.0.0.0:3001/n/"), dataset_universal_basic); +test_implementation("worker/name", http_fetch("http://0.0.0.0:3001/n/"), dataset_name_single); +test_implementation("worker/address", http_fetch("http://0.0.0.0:3001/n/"), dataset_address_single); +test_implementation("worker/universal", http_fetch("http://0.0.0.0:3001/n/"), dataset_universal_single); From 406375af32912608fc16714ba9d082688e2b8dee Mon Sep 17 00:00:00 2001 From: Antony1060 Date: Fri, 5 Jan 2024 14:00:53 +0100 Subject: [PATCH 18/26] Lint --- test/.eslintrc.json | 24 +++++ test/.prettierrc | 6 ++ test/bun.lockb | Bin 7021 -> 116945 bytes test/data/bulk.ts | 185 ++++++++++++++++++++++++++++---- test/data/index.ts | 12 +-- test/data/single.ts | 114 ++++++++++++++++---- test/package.json | 35 +++--- test/src/http_fetch.ts | 3 +- test/src/test_implementation.ts | 15 ++- test/tests/server.spec.ts | 75 ++++++++----- test/tests/worker.spec.ts | 61 ++++++++--- 11 files changed, 422 insertions(+), 108 deletions(-) create mode 100644 test/.eslintrc.json create mode 100644 test/.prettierrc diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..6047b27 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2021 + }, + "extends": [ + "plugin:v3xlabs/recommended" + ], + "ignorePatterns": [ + "!**/*" + ], + "plugins": [ + "v3xlabs" + ], + "env": { + "node": true + }, + "globals": { + "Bun": false + }, + "rules": { + "sonarjs/no-duplicate-string": "off" + } +} \ No newline at end of file diff --git a/test/.prettierrc b/test/.prettierrc new file mode 100644 index 0000000..59070c1 --- /dev/null +++ b/test/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "singleQuote": true, + "printWidth": 100 +} \ No newline at end of file diff --git a/test/bun.lockb b/test/bun.lockb index 7f24e54a8407b5196c657897a514d9edcb53892a..5567ab3dc69b6bae4888ffc5ff664228ea5c7e96 100755 GIT binary patch literal 116945 zcmeFac|2C#`agchP39p}k~w9bq7uqX=8OrM$7H6+Oi?rlg(OmnkV?ivB%z`xb7(RT zsSGK7*UG&)Z4K}Db**cyz4qFha}GbRM1Y^KguR=mgp+3= zk9~kA6}ZGayc`ZXyLmW?IeYmYvhx=Ul%yiT;c&xZYlcTTXLfx(JZ4qWr=4))4xLlW zxzq&F+eh~B4&2aIezFGi!r`{80FL5U3HN^(K`2viB6RpwX29VheEe3LVgb;A@+d$$ zfPDaI0lM1xc^&q|;lcnBmXiTe1B?eq39udXgV+edWCEx_io>l3=;Gz=2%2z$pv(-) zdkOu3PaGBO2apAzx381Gznhcq9WoqlBPcuhdAND{<8aYn2(Vue0m*<9SoR{6OF$XM z@8J{(qQl|x3GJ~2Oa#ad+8v#oyv5x7aJGO)56b2MK~qEt7z8Y@2ZM&XQ~=?)bwE4J z!)Aa20Nwq>g6s}?;Ou-|yzPAboYsSS*w4*R%*DgY9;XcokWUNhVP4!^JiUCKa5y|D zLqD*SorfRJA2@^M#kDK_oZUPf#R9x>+rc0pPn3Y(a0%G?gP=X!4!QZ`L>X7wM*$Cx z_W>wFf4**qeC?cZxL-kl@%ye@Y4`GU0N!!9!@hRj-a$B=o2RRjubaQ0o2Rpfn~STz z3+M;^9drr`g#Bp1H_lOwJN_z)L#c>IRB1b4*tGwo=%QV_5m)S-yy=f%K+`r z|6x}*2Uk$<=I3+-4nQ3&qF?g^CK0YXZ##ciF%YzatDVO|(B22?;XHu#DF$M9c>&5$ z=jw)){+|KDcn7$7e0m3X203^+Iz@4;#N`SQbd5L$;$Q<901)Qc+0M`3 z2l(-I3h;At4#FvLu8fzKYh`>E0AYMTKt1el4tSs{LIEI=9T`+TW~nA)FL(k1VfB) z^7Zxd6>~bWO>jkriO~Lc^MU65@8+c*=)k{1! z3?S^^3lQer6(9{jG;UufKQE6!C!C!)4hK>nVGa6da6IV#`@8$j zTg)Gdbl3>2<`W_;c(!VjZoXZlJ_LJ75~!!;dsDvLd?m| z!BuC+O8Z0559-Dc+HK@l@;3tzjw1y0gE$Dvzw!?dj^{C<-2@<9C%%9W{T>7TVg4*Z z8Qvdi1e7MUuLlU@-3t6dolAr`aG-uYD360O+~@lM!g#^H>Fea;bfg88VShJ2J6~VB zAlyArhH(!nuhh${tgQDNpdRKo2_T$LXF@+SfG~aqfKXQgAnYdq5cZ=72=x{~TrloY zfUw^o^_B5-f-)RmEB{5v}N1lW1taCS$W#K4y9q(rC}0tk7G0HI$}LOsrKC0+)w z-e7#UKpEx%?c-?Q_uq@dfvqLNpYUAp2hVAHaDTw(k9`mh=jZY3_P`x-@^x{N#^D|r zudK5}CM*8#0)*?%-q+5-3GBndrYrT{0AarC0K#_kd~|g4{q=x7YPM2e0rcQ_eT`P` zZz~WFj8g$1yq_ch!ujF^2*>dazmj*bHh=A-TLD6SRQ`7zG`=(YSMK9~KPMnR8P@xm z-PQ##vmJ?cK6L5xGg{V#{WA%Jq3S^@=~WNaX(w(eW;5S271Y`tPkBOnuV zJ-U`BcbpeX=B;1Xbhjz|oQg|JZp2}>LF)zgr@Kw3yW{B>a|%dE z=%-$p)xCMl9N+WS^l8Jj!!gbu#SH>vsV)um#H@X{Rf3+vo;&gG*qG7drY@&k_eU0z z^Hd3?XT+<^#Z{yQUp`zX|J6qKTubKhD%Bf5=yK^ky3uX8x7SZIgelwpz|eKGy)F87 z3?+@7{0eK21;rl!YHgVC!>?Ib;`*-IkH_por9JL`ZfJ2Sp_rh#+eV!Dnq@^CQqk5Fl{d`ME;1Xl{7~A@R4i}bdqIKhM+d9aG zQ?_}g7SSw^@O_#0XGuFG#ATnKY;W))YoMd3lsljONg`$cvErLYga+dGzXD=NT=-Fpv9vx9=7_ zJ#>0L$6!|DhNyXu@w%~2C68}Zmm zS=hfMryYeX;h$eXU$4t%TKE~$UY zntqeI$?gp<{1ZJl_>6mPY94JuK z_YUGbR;RElZts;v8;=lHG7C<9&gdqmu+hW57DLw!3SS(a>|~dHQ+ku$!RT7n#Daal z4HJdTP2PQ1z8(9yX`F_KojOWDdMK{f|F(WJh2MSCn?)q_pYA4P&?Y_3mubroX6QRU zQt14q{=}U-`O+RVRROoO+L~GRd@!Rux9Q!?qfqaR&Zq6O7m8nR2;rTrc5WcIDu2|Q zvHy(txbR&%Q}d^LohHJx(0Z&>nH+0;SDuE-uCg|{5~7nS!M)rJPaVX975AIW*)h5lK%B;a_^6Z z!K8YoX=O{s2iUvcoP2X}hUsQ~UmESsTe{Z6ddmKrTQ*hH4331X-R*I*lBe1+SV`pV zRxuN5G;TquYpon30rTH$w=cFh?RAVUc4`%tGpNXM4-5!ew&dKu<>9u-vnDTRLkg8@ z_L3Y&_ut>$cai2IY`OQWohfxfD!yFXUR?V&Q1OzSeuwcm>o&6>x3Bfx22zZ4zO7|q zq}S+M>t_ejW~Fn(bCuA2vG;3<+>M4F8D&!H@9q;tboZ3)&BULXdsfo0{4o4-YU6h! z$%RNpr!)SYt)F{6AD9LgE63<7WSc&*dD3%u=Iev`;$%%y%fN}diML5;&pyY+KMAAm zeD-E(UxC#uuZK~Gu>;*}k&FXnIKI(i@zrhTsk^_V>?@`A@k)ysxyn^LG(*GY?)vhZ z9?R(?yvyCs-ejdwZR4&;m}c%sP_52wnIttmeDdS@jdRp*<;QfA5^2ZmipUiGb6?@) zz64&;j6X`Y&c8hCqDFfDt9|*8o*Vc zzB!xDuk}cgRo_?NoS(V+(&f$<6W&T8H*3S?>2Ay(iQSjRTXr$gSfK*AV(V!NXik-E-*3vC?+m<(uDLzfBO>rW-6GetBBF zo9%-5x(Mg@>yJH2tKOIQuFrf&!D+FI{zK{ac@yob>qLwyE*owR-Xs09lvAJVWFf#zhSQ%)6E?bFY;o;RapaRDbhO+M{JJwoAd??ifr8BEW+lUWMv#Q*#;P}E(JbpPiL#PB+r?t`?42n8qXWv~B(5GV^BZw(hL3(|zI*pXt){i?%r5=* z+-wuWtrwb>tgw;id8C3!=cV^O9)+;gCOer;2r?Y)A1;;Fns zW6FHuN9Sk79@^wO$8Dy>-OA*bnQ+{1+OJTnHcI|{oQ)~u<;H~w>XMp*$Tzk!Cb2T_ z;$9ri`N=}ZQ+M})-wF403zFcH-x^vvzZ}%w6P_^~2nX zpKEV7GswE)_eq;PBhPOTmeBgXV97j-o{xTy8hTq&rd^BrswQ~-L^nKt|G+!-^w$BB zzT+XOC$@Sf_U>78=Y?&oC)ekiS6!RrG6V06xxFOweKIOj5K#PP;BoDY@%T?dWtIg6I^)i3TN|9(zDe%65V*MDQ+^U^N^$1|K5ILCq4 zVR#`z$cc6bW#6yjDHwAsz?3)4pBcA@WU=l{z(6h@Ne~qZw!9eiQ&T-h%v-J^>2Le z+Cr2eeh>ITpWq+*Uafx9G z)Rw>}hKKqfektI~0zUK&;vm`({}dp?{6QkBBSr-EZ6fet?x3x9{nCR;NAve5d>sNG za^d<}?fgFiBrQz-(Kw*qKN%W74fuEg?;qF~ETvVCUjgt@{vn@OL;jBt_%QFVZngO@ z0eo!`KjN=;{L6q3KHWsXxSHHb09KLGe@1pknS>VKz!`dtBhCBTQdgL8-KiRHfq zd|!8Kg9aR@)ZCd z&L2E`u7?do8uA|s_}~+N1f(&83)RE&KN;#*4)`#CXx~Eh|HOrDsNWpm!}%xn+=g@! zUxi`C|7voF^&dgtqxg~j?-2Q~27H+RKaGD5@ZtE;{`ot z@cE0zzuNn!5%A&t2jfSYzw-^-QD0Kduf{2e0yrht#`KUfFPu*4YR#{<3w!9Vnk;`p5g z;=dsHN9EP>*Rrg{4}Gs@4?_Ml0UzGKkV~v@I5xzO27DR7hk1i*XSMmS1$=!%{@~mb z8w2uB$GYESQNLkA{Hqx| z)JA+>@Jn1n;Gfw02E~Q=K7bFOU&N04-}!lfFHP_dHHeJ^`R@gMQNV{~xOPxI^7D6y z`mF_r9(e!5{6iyl9Ek4;_%MD{M!2H(_y3}PS^viWd&XZ|5dS6M!~Fx5|Jv*COQ^p9 z`^xu2V#kdX5#I&ycL4v85A(m;`}Z>7qxt`n`5Oa#HNyO%m{5%Wgs89hhL!kO%4(NHsGrOJ}R#^e?I^p?!PGa#Et{`lLLROA_4et?**@tzxH4>#(zS@ zKM(kD|Av0ywc7Zb0Uxd(sE5|jKlw#%$Un)(mHi9W|A~Jiz_%j!CpLEI3;8bsd;`FT zTvP|!e`l!g55V7z;lt+$F^2dC;E&JX^9T9|uL49H;zt4gHUb}#SG#`70bd>Pp&pEZ z*fRw3&(4LzDG~gmIw12~Lw#KVAMSrB|HR&dh+hQwF#o8$+W02{-<04V1`XpN#*jZ1 z@W+MVPmCfU2Y$PP@8QH4;@=|hQF*ogaXkOs_h(taS0dyO`d;n&Nd|m$euMng#{U-Z zm4JUZ@5IhM=!!%6lILCF6B{?=LPPwcfUg4lqqq^S^!odM5&tpZ?tJz~2S<=={A}|5Jbu^9P;6XXk49f}2;)A7p@wVuD(~ zGt~DI;3NO5nM1_y1bhV$|DXCd+Va=m{}8(lkpB$8w+H@V8Qw#y&EGQM!}A;DK|az2 zKmMbke#(L?^A9`2y4Cs*2YfZ)AFdzpD!A(NM?2u7_cxe36c_mM9}V>*0~;6g59beF z#2Vu70etlSL@XEfL;Ogy{}RhXc|q~769Ip-4*1YN_!RT&cOa~ z!!ogR2Xzsj0(|_i1$>k@V#k5_CjcKl{}F$+^ZyO-_h9(MzJnnDs$&28et}|unYs zKV!hRBKU_f5vzgXzXkYk{N#YUntK@Whr#6Q0zN7eI}fP80eJYq_dhs()<1~Y(ZT>?5A3i^ze;Cth^H&M@aQ;yI#6E}6 z`03>TI(}3SLitBS{rmyn2;(2s|05YRqJE2j56{mies~`eV~D>M9A2>e)#mRI;A>(0 zuahxVpnit|AKt%zn*Vyhhvz>uewyDo zCbT2}6kzfHH~Z&-|B_z<_}K9i8UDZW3^p$-O#W88eyji=tsj^ZG!MUv1LZFl@X`K( z=HPc+*pB$`0e|0r5x;`czwUpiN$fKO`9BT#|IPl}0r+tLMPpy>{X?Vtul$4XSDk+| zzz2`O2sED6?!O6ukM?iKh2tQ`kiSoa_)&Sad;^t#egAq z0%WiS{+d5HZWseGhWc@;{wsfwhw6W)f%^FXKHUFN86mO!3cyG24}aqSC*Z4K#t+Y7 z#2AWGS?#a+BPE6m`XGKN;G^|NB=uJ}#D4+!X#a-uzuNe>fQJXV|Don;*Y8ol2O<35 z`dRJzs{wqt|D*9B{9P_kKWY&C0gQiCPb}XJ@Ui>v?|7&k`FRNVX#6mDt9|~EY5sNn z5&I5@{M!J&1sFe!3$8s@_yZA!_~iuukPENX&L0Q3c~uDc|C8}s1HK`F5B;OKem4Zv zuNLs(`!`y*sGe9p9r*YO=MVb+llYAQAN~CZ^hy6a$AoqiXEWfV^AGfo>VGGL`iX+W z3wHm&|Bm=?{?7qE)<4VxF^2q$fR{hGf1~^({GA5k#{xcl{-82KV)^y|#$T=fpMWp( zU-;h&lK0>6T>u|`e?f6m{chldc9f?|z(@N(Y(n+FlR^C$^j6MasQfz~YDfL|13uP2 zYX2P~elg&~^E2G{U>&L_mOlmf*!!R2cSJ%v@}mIA=>CUwXdZqigZL4EkDdS3-ajRP z56{o=z9shA0b@h{CkXMQcvm}qNw9gL`TLXo_c-9g`xibph#fmN{yM;i^AE=kfB#Et z4$%0Sz{3MbL_keCaIJR!>;WGvp$HDZ{ge6c1bldYg5w9{Algv;se5raB?2GDzS{V^ z03YTbuAe{2KQs7v1kX=@8h;|-!{47l-^Bjz0CfGo^}82re&7@Gul?^&<}U{D!6(!R zYM@DM?y&x!0Y3Ny|0{l2PpqN&`w95){)fKd91t4=;%_xs`F;lT2QO3)e*8y6{SE^@ zntw=G?fLCK!9Uc4{#P6S9N^1f@`vJrvHX*veln&j@uT~Gwf@5ZUk&(2<6bSl1@Phb zOXUBbV#c@ZtUsa}RB`@y7r@j32JO)fn<$O^6?s ziS-TrApUp22TS0O_YXt!|8D<05BMO2KlVQX_}~-xula{_NDc=_grV_o0WXhm|3~YG zo(L}Ng7}94A1uKL_}nJ;?1$w)1$=n^hOz(2^M@I{Ji_s#xo3dlL>P+S0PvxIm^Vfu zc&H2FUjTf#e*Tm{4)`$tf6ABIzw-S1Q~pK3SHk2E-N$I0e}`!Ny?_tzUwHn7wpu=m z<;wVx9-7A$wZH!t^|u6kc>k<+{7@OLS$IL8Fn$<24Y;5|gnJTPPp}=WjsJv@#|$pm ze?7Qhe|7?H00`T`e@)?+VG%y_1qt;KVLuUYZ3Y)O7X31aP!Akaei=kKR&d+=GKer2 zU>bh)1L^o>|AbJ_3S5u}&Q8DVpAh!9A@su{thXi9{}aM~c7%QqVc8yB(7z+Nph1N7 zP6Tu&pbJ1~5MjG3xL}?R63QL`VLp7p1q~wX=LZYGun7GH{Hk3+*e{S!{wIX}4iox8 zg#LpGWr(o-D4`4y`UwRWtPcYhG>EVqPQVC&&?3ME^`gNA+hf254I(Tbg9Tt%g!-}I zg6(nOf(8+mPZ01VKxh#0;5rR1*nS3F(EbUbej>PF|8wAi1`*aLgA3NDfD77xLO7n( zU#)*dc#mZh>LEhC96}i))V)e5V-fbtCDcQN&!~Igg8fUu1q~uBml3cWAT)@uz5*72 zL4@($hXr5|VSZ}C1+5lbu>1&I(69(~8h+KTAdIUKTu`qWT(G?bT+pxx>z{%P;xlkT zg9v$TumB8;(0>QGp#EzDb^(NjMcCf+t9AvUUO%CXMdtNiw2<6=Xp+SUxRS5N}0HKZ+ zK&Y!rXg46B5dlpBLW2nT`v_=BzykoGKU;uM#}Objh_Js40o@7p5Me)0fUwGofZnhG z3?k(D5ipR@ei$J169f?QLkaB>g!V{6IR@Zb&<_9qFJS*ug!;1p!GCax@E-y~ofJZQ zDnMAy00_HW0{_7LTmcB*gNq5}djQEnxe6eBrZxhEycU4a-!p*F{u9Ec=incRtpsc% z;0ss@42!VpC87R5A@uv2!0#kr7ok5yn8#j#aG!b)5XSeB&<+viX_8PsO{j+m$M=;` z#v;teH$wmKgm#GV#~%b-Cg4w42@K+|AAxg-YhV!nhOmm1P!AFQNJc1Q5%S4FJ&c2z z&<+vC%LouwF%jw^Qh+iCK*-xjKu!X30R;c~Ww0N-U_Y2Em^V1L&>+G*|L@$ik{|4O z3FZfuVcu-P157n}!2a6y9z>zxSbOh6Za&>+HgS8&03`ro-} zB_DAh7tkQW{G1@*|ISSy5y1X;ZdzFv*z*$13oOI?ZxCE?-3$@%Edk#Fg!Z2h_J9AY z^{)u^J`(C7!hXYqGDKJ&A>jYcO)Jlr|DBsw7Rdk3O)Gi$|NpsZnI4|?{=c?0xD{Qj zG`?i3R_mekwUW_~s!X}3Q!Z`){Mh`B$k~heac0`Ex#t)|RP0$7d&!esoqKMHaSW?)ZTmTN zgM6=9|ATm|+uIt~xjC9U&nIxTvEDHE_uj&rADyvoa?D5n2YU?=Lh-`02r~S~eEwG~ ze1h*pDfo5F4I|^`@SQqaPsndc?Q_JP8FyYEJ??#M;^ZY}+Zvl4uTLjuiT$8%2$=Vf z(u!4<{j{8X3&yXL}Ii~_XpeNS=$b>spMp>eii$fgB)}d*6V=~(uHSeWcUZ#hlET`T)+F_*R0{|p-L*&*ApU}HUHO58h^^kb(pLi_V@|U2m!mdHVGTOy~-Cosoay9JKyLt7HEHMwbGsJC(gF z?&s;naYYQrA{@m7z<)2@*VIJ|ioRb*_~HX?aZ$>G}WL6MkiVi8Z9iih5xYNwRG zt}s`U^{{HwHj(2PUHBe>3{M@@*6_*Vk%?(mI=SE#g%&@$b*BQqbnIfq%gl;qE;Q|U z`r3lMYv8W_Mbn+jG|bmXi_UiM-6*Y9AcXQjg+u}3&5q}vD&8Y3P#B$% z=$&OaMK_b-)?)6bkX7}{lJsiV{62@AM=T?ZujlVtbBfhp?_O_Xb=&MJ@A~o0qNNYs zN3vjassEze@IB}XSKVAeJNxuWZ?3QRcUa7F?bxYdFT`gN)wtN5-j=abFi?2Ez~y6< zwsJ=I>2sz}e{BAsxns9hTudyj8AcbqM?rb~;UddzMjH-ujC##%bGv7K{z|@iX4&cY zE-t2pNjGJSV?xS%*)$(W-AH(Klf~xM`!TxF83Tt+eo|a+6Lmt{>}oN(v`~;>c&4i3 zi3;zN%>CE$z1j0Tt=OdM3ytl!Lk2%AdF-ERMu}v+d0}+s9lvA(*SxaITjL0qt;zwX z-=Cd4(@S#cHmz4A&_Vfy@A}B_S#B~>t}YDc${mDuWu1N$dX)O0Ku?Ll8P5FKjuxx5 zV!kkK4uejbd67bn1q;D=E;&7#ygQPrOQo6NkG)0@!EbX&7yElC{I!6L!UElw6G}Sc zdTncJ@YYGqd9m_(M_ly37}bScwd|^3&Y#KtPUn<&>E4@{iS#r9-R3@4YwkFnln%;z zu?2n$N4oI66d6AM+0=kV&Zft0kvRs`talF=v)<&TCjT~Ir@;3@IVoX+G0ZW5{ey%o zsZM-hTd3ptN0-g{7J@<_e6VLZy=~uxVjzTc;kPPe_h^M-k$t0SJsa&)=Nat^^()kOMR_xBb&fp#YU0O#9jVUJR}}B z$>wH8oH57vph9o0^v=DJJq;E7b1Z(=>r+aHPwML;>RS-x?k;d1+`rCmL?F zH(0@(di~qx4Vmi&NA4P3e2(uZwqk18F0U&TxXG`H+waHuJ<`UkHDO~rKK{7&(n(60 z3cEkBVRbi4-e^t_OUV0L`htvUqg`;@$s-p-pEK}~%vZ4Zyls$gp!HgG-6Ll6dMadH zygC!7ap|+ta|7N79}IH#`K#Eje7j%CFMK8;!_x>%jl>S+HHl7c({}LrbUmapKXu;+ zK8y5w33PWd7@1F7J90|1DDu&_+TWk8U%EE8HLjVHcJP^)hqzsKmje7<2Fe5ceHAkN zV#!>282z27O2mv9ay8(#;#2+wE&TjRrbs9V6!RDscP2%y?+brnwWd_oN(~YJg z!|OK2vpm^7O`TylsF7wepi?l`nICAmiDjwUZHj8T{RaHq3ex4k>Q-x#?kcg!F<}?0 zSX8@xCYSzoYRt}$`KrbbM@(iHCE z6zyDj=UU0{My&3uxQT9&r=PZeVB~&If0*tx%Xzu;i!-!^(^P8R66PVSOmQc|to9jl zIKOL&p6WOgLt=Q!SVq-$yLzn|v+&nG9!$KPSlz7X&*z$wqR%`n5=^&ssJ^0JZ*YI| z^7rlQE_(eOS;yPXDO~6Ja*8B0T7A>^>LI>_a^d6k9#R?f$!brwmp!gIgwf@~>dHB& zei|t=Ygd`$dNr^YSe ztNTNEdZ<^jiBvf(PUHG+*U{ti9EvXsmB#R42lYoZG)uEf&ANmGeCnO+m>)Tv%93aF z>9M{nS$x>l-?I1Iy4-wBynI;QH!+!plT1h1A{B*Hz0)^`u1_j1%tsQT62^-~niYakYDpcRyWi;YzE1v_$ zj`F(+t4sEJq-FGI3GH)s&tqqJHTSD7iVympF?m1W`>wfgL!)Sa(!fsv!P2V3t*`L8 zhmB6v6{YORB~PZlKOtCPCj1Q>uK-rJF2mw(yKBSw6O4QsyvegP8mYO~WI{Z@$k=`^?Ui(T+Z*EdqhrK zUp>|gf9Hkrum!8j@`eY;y4>_IA~j$9(>v}GkvwhT#gvWi;m4zw=l#7E^Zg=Q+VAF2 zj>k&ZFy=(ukh&fvDCz%QDN@cf$AC;i5u+=J)omCxI>_5NkZa3SYIb~I)wM$lF|VIp zsL!c6my)ic`j)|({;1uj?eFL2HWc68l@@T?nb9%w0+_t(x9|stMvb&Fx^T}#hTn7N zBCeZuT<=wU+#}~@MX?Wiq{E~+s5zcx2o`8duM;UO?jIJ2wUiIL8^(BIbPeg%k6Nrs zlbJ<(G}&)mI5Lq9gis!Yktjg?W~Zkv1>*PR;^TZaU+BB~Jg4%)8q4e-T4$E9`z>HYC&2Z{MSx zYvS*VeR}`8P`lZRKgD(bgGLJ;cZ{wWR+ogSLRpi4*5aVYA;lA}2FA95?NI@b{oY74 zF{JGm(SBO3z2oa010H_CU?1&`8|)S&)Wu^+e7~*JRioP+LAM$EoEOLH%1&o8o&2&a zaQ!Nc>gP*$`{+-#aRjLi{M;t(t=^x)?6dins8smH3Fm!BH1q5qABbF{`T1!e*G(qk zY`;36%61qd%C7`g*JdNdjB2(i1?%-2(GrurFKc#&#|EznySqm}h;!yhQ(H`hZ>L|P zmEF6dZ$Xd4I?8pmrhZxm!xRvM7;;li9t|V4>y0z6>(L4E0eWXJD-75Z@U4E@P zo%9pKOZTO(YLHWY*faHF3Kz9+{pMLHAh|X) zS%Ebua`0ifm`Bx5jIK0RH*QXQ^dr9)v*(+^BR`xEuX{)#nW%e%N-)>@#gcsf3#~p9 zucz`?q8<(oDt?|1Hu+o{b*As8i%E#_Wd}_%o>$;cUH>n?Td}%gIS%(&lF80gKjm^^ z`t0qKPt$C2Ch2{(qo49Cg?^7Z-OJ*anuC=#yy;x-4p%-|rW+eG7NVJZex&R6o%Guk z*nLt4tE(5Cp7CskgH+>oQlO7XmjczyyG*G|M;7y)j=j1qxaRpTAA^w#w^L#(IfEBQ zJB&30hx+<*b-m3!8}!ZnZk|xb#4C%{Ewm7=ZhA)fGW-f-#`t>rdj61Lwy4KX; z?-ymC zRH+R-P|H}t;yqN3E4oi_Nl~)^5@}R zJvE>Ze=Z<->}Ss@>aR-PU#7d>oGxagfBQA6=UM}$CDq0FAkIh8{nUj?JQ!U$tZs&h z|IJvJWy(oI6?qUO7!#yzv=ALq$Tq#f?z;}^=T zR!!bvvuQm3OvmN<^9Cl8%)!M2>kJem7tb8MN1}q!mB;EPzdjLhrc^bBQHgz{w@R!Y z=^3}fCXWLx)|oIlouXk;5_i?k>@YahyujHy7x$#A(xrW?`~|O$@GZlwj~OZtFJW{Q zu(}sw^1jP>1fTp-TOISjx?2DJmhr>7XBWDcn?nz1@#<>4`jTg$V`Xyt($w@UgM5P2 zy@$#}e9!#`izr(<1C^CG~(qp%<-S%c>mI?_}U)tmXxpZwe zHxndS8`&tT#=;)f742-87QAqqI?n8?+Qh&%%kcF4jH$hCmSHRzEuSnj!x#A0evl@K z-`UbicRLE6oscfPr;*`zObT(kX^$52I(u`vPid7&weshioS&6XXmDvzV=SW+thFvq zYSQ16JGovmX?J8+5p9Bp+iUq(-$g8sEPhIW-!hP{5)uW7&tcI#>mSZ=VUFU}uqCsO zWnR`qMiiwcd7Hf22KzbRSArd%jZcOe7zu^l`dNM}NyrRW6Bg=t`svYhnWBXJIPCke zGFEr!UJ%)r!1U{Fx@pzs7Y!6Umz^(W$Hljo-#X&^?CKR zb?^E;LcCqmoWFSH@*m{h1GG@QaLpmZ4;0*{3)7q!vQRNAS(e(9c85yuwUScMAmy;e zWB$!oJ0Ced6ZO|jT6EbeM)gi4YbnS1%9B#kOkEjGU+ovvU$Og{DiQ^V4-Y;&7b88V z7!ku(!~IHS-ikUkONG*4kY==5hMfE(hopS5j78XweDcbw9Dt2(oTCg0Ut(^^@bz7PL1Py99K9=Ulr8isEuY-aYSZ{Nna&i~*L#$1 zi#`|aujm;fkZJpI(0BNpH6v5da;9Xbrrwg_4TfvGX?u7Og@cX|24F)YM0mg=pEt*IZySO)zHsny>h>}GrgaGJawFu`9f%&R zgJ&ux-zvFV+jGqwL)v2{)!ti$BG{}HcL!u;J}tkOLT9_^yl$ z?>`>cG4(+4@eA&V}Z) zDVHLE7K&FNtJ@@T_M_Mk+45s@%_D+d8zu+!WF0ibw#C$wvlrz$c%KWlDgHA zcZzOiE+)2<8kYht#L>AvNU&~9zj3w~qicZGRb)BF^**_Oa8fC0I^(sxKoeKlzAt4- zMLQq*DSV#@R$A;%3(3=L;Q7RNmu!sh(`)v<&m+UDIF{;Ux(>wB*d$_f4Y9h#ZuE^G z9HhG=MjGa(B6ct{s)-t=xx|zoxPDyD`__1i@waXB)dnw`uAJYk+Abncm|BwXq9#2+ z?a_lOSp|()Y`lB1x+fS~H(jGQQB|FM(JJ5k-ea`&I?rrmZNoj+(I5wvXbINc?wx)t zEuZ8!slOK(8GG2}xonu;6ZOo!Gto2P`Qvd+yhd1E{GILk*2b28(-$5H3<#Gw6!9$< z2t5ogSkKCOPCKDAurul|>5|}55lhLrA<{74DNF{4?B_ zd1=+kdCB?RwC`@>g5NUGdN4tv0P$x-4)$(yv+r~+=y#{vIc_r-P{`@XQTyo|rI5(p z@}Qtw2cB;B*0a;IHM7(GP?#&(`91A=t~l-KCHvmq?V8T;cOgjE6stQueTkLBsxfr& z&EWo(LXAxNR=!zzOOtLqq`5Q zn}0Uq0ek43-qT$3C+~zcUaH$$z3l(`$_DR(w&dw$2^y>UN#S0S*_w!f>H(SWTZenn zPm|YK(+TrfIfgEb&%kE{iq`_G%Opf;@I_Ww=CyB+TSNp;D1S-GHBTq4p@`i_S|B328>ht}D#j>TEeDBz=ZRbfi7;n53 zDY10XCoxnmus)}8U~pR6EK@mL^RiA$O!i5+n~^?~uN8OV$q(Q}l`y*2Slu&+Ux`z< z=Ul3C4@rHJbX+gWl4E@M61fD^o{Dt0jjyDuFEa?`D#c4ieR0d=^fXo37J2SCdv%i3 z4kz8KnRvKWeCG^3`=`XSirgmCnw2 zrK)MtVu`oOmTD%4*Dldq@)6c!I)n@5zf`fDkjS_{rdr+M-q&pXVIed1d*AM}0u7X3 zTdZzFX&v_k6+3<7nKJxwj_e8FkePrf%ise3K$)i>)rIENIGfGl0(NiN_D1Fu19TRQ@Tz{^Shh&B~@)O z>P^4IGb{Skg^T$DgS0z0!&3UA5PF@c^gPEp$&}9=Gu)VX?XkMgKUlNgr93$pt*lD& zj68be%G}J8;ryJC1Kz@M+7A@EYhw7@wOq!n_%GKfy4FWEcrEEQ+Z)u?TbjP>>tu8< z#OOL;b+c^4jM;51o-mxF5u9P#6%* z>MP%C%vag>ow;N>KZ_xDgkQ`wC_N=!Za`SHKrv?M$J*FZUr}3(t`k<*xV!Q5QWduDNZ3Ed!|nxAx)Ry zFZN7YFE-JzRMCg43K{I!E~Ka@(i&j;YD;?l+>Ql|E<6Jw!+UYOF+UbkbDrbvVKP0% zaYaFP{FutQ?al4tf~KZ>YU86#n00A`TbulK=3Dtb`f;Y{YJ!&A82ASI$_ zyC9^0Vz^J64#tKo-y z9qWogSO1GQ?$=%qc=53_@w4`cb=4hCSAK4qzF*v!o1k+`*Qa4z06rs7yoZn|Kzu|C zOR$aVh|8U7-(NNPPV7CnfZb>^|<<_bL&j~Ka~#AaXCrNXZi%6 zzxYy{k!k?XPaD2R-IjUR=b;Jy8|B|jzMLF$OZ2*j(e=XWj(+Z28^Ix{lR!}sIz zYtpr|#wqt4*Ivmge^PWqOV%{IC6}bJE?Y|ZG;@j>UCBDT8#7Hp5uL{utlf!iy6b#aTmapDY8_*Snof)-i8OW-y`eH$H4Z&>=Q~5W zc1xy338h%>@sUd9d@hv{(IwxUF13>k$|v@|+8<6~PlbsW{%rv=e8d5-92c!K8k{nZ zcJnl5#j!I4(3Y-eR5!cexanuf7226e?}g?I>Fvxc8p~I*7pYA-i(jTH1qyAvq$k@{ zc0?Wsp*$Qxq5$zJSBww(S}vLhxr#^M9vWgRGdIg_4@s(1+ZN-wB|PwMc9e*%y-rQa zxUNyo1=eFJ?a~x`%SZh~3%|YQA1a~2evb;m>hgEZNQcVnNIa<>nSW-%a#HVv{({g! zAyP-JEQzZ=BLg}fPm3R_C6qBTEmfFiNw*yKE8`8JtXnF|Y-L{i=?w`c-e9cmtKxHv z0>Qg}Ug_OCeSO1WdhR@}-2yyIAr<>x++tuZTkO3{zVX}3N(bl36DscxpShe_7^E0} zJr7qmF+4fxCJFCdl!v2O-OZ;jZP6!56P$V;Lb*q5bnl1U!=Ju{e)*6XF!(UFI=Ik; z%KdDuVAzqW^>Pu#4g&%4cKc)F0`RINjVF_0oG0Qix^QhE!w;<0IPu!|X41p<=WL>C z75Yi%)M#f;c?OL)GT6?d5 zBwa<^O(%VLtXVl`>B|SXclBdeqLN=##dtTfayLyc4^rACPVK}iso(R+FFP2L?8JX@ z=dpMeI>F;W3*{jKt2^}iGyl$8r@Q#Jrq8_AKge)Y8TT?baM+H6N6me!|Eo{Z$FeAn zH&bmFzcc-ocHWo!s-?F>SAKj2>C#53W7M>pFuL#=iVSZ=_aL=BFKjk?&9k#JGH%j4 zJ_q&;*SzS{YFHk!WPhZ%-zC?tAX2`_s+0UxQZoy+R$W$mb%f=$#E+ZZ=vC}@0wEM{ z6cPo9zxi&>Y#?JtR!b4p0!Kz>+LD{3_)n$EluK>S44UrG9UMM%lI@tH%W1y$FmbzK z$^2q!Zifxk&Db@WLbS&X6V79Fqp`XNc}`Y)jmGfa-l@T;#jnuAGvhn3dmZ>{iaXcf zp*l5RnQ(-s*y2cNiXr}sUL+OO$q(V#UFroMZPpnaCuZ)$Z>T8V7_9EJkZ;O3yXxmx zy4*M|rl} z?$l}V&)G?8^{~34taN;Pl>}I%niewwqjDvM+=>@YcDaw?l4+ zeda31-K_6vmYX~?Rt#8rdEsM`X_D8Cmb`5uE}Q0@q?IFp5Yjz?L;>QZyQ;&x-8OYf zvc;R3w>~eFnPl6?9zXn@HP3fVk9c986^otWKI`gjxLED=?da0^W30({N2TmYWp-< zhqg4x?p!FZw@54GYKdJuPcnX%bAyc!eHY^;rZmp_&1l{@R*^y>r8nuRk*Z&k1F}dDGLfm#-K$;Gmg_| zBU3I-JyZ=--SbU0f7YE9qk9UgYpTf3pU1A=G4v_nM7@8)p%XP#@7%*`rPigKZkzvc z=hp6{Z&(|d@)Qa}Svb`%@D$1!K9i%rpV{rswm#8sZ1Dz0_cT^ld4~Dkz%7$Q*ZZDtpgaD9w^1O1>2qsO>#R79Ep_oL z%MBRaGg#eemzTjubnET9rDQda@F)rjc|Jx2E|R@d#}r9&~7uRCQQNvFK{X$^ke(D#V%g4=fv z#-x4OCPr?1{|9Z!;M^Y3kh@2?+_y>xm6p*ZZSJ40-_dA){ja;>7 z1qJewT17r7axc=qz3PAV_^w;apX9G@WZ&1RY>s_yCSi4do=w>9|3&jy`>(na#|7}Rhi=1lhYR&cCEM6%}-aUhG#Og zev`4fbmwxrwQ3%{`N>HVReIhhnf%Gk=I4Eufcn66}-@{39hEppHPSeKFOwKGZe zXnbv3yo&-+>3W5>8Fo{w6~KQ^6l$Qe@Z*v30-);OmDE`IE-cLjSA9p8J5ZYoyy?0~gE)o>GUhv*4Unvk+?yB9{2 z&!dY&Z#EVH~HlOjl)IhIRNgk>s!E1H}3~KW}@w*%70ghSl}n zIDCm-J0U%Jyf~REi=NG;X zkgp5n_*`6ZRYvwDMmHU+n{_QyLDc=)n`6mUkIhX@+PQkqF6L772sG|^8Xj%+JYrb6 zj&!<sC@CVH>NIISr#T8yNF+m1WTrwQD$Rv7 zC_~X86+%iR%Gm!|`TAp72%_6o?|$}to0#hq$M$b=xvyvE9_o8z7k{%jd&R2{k!nSmQ=)xl z-jO7Hy^Q0{!1KmM)+BxO-FQEz_~e;ZO~-@h-<3OyDZ(nmSu-09lnPCxIGq>sdwvqj8^n)8NAbK* z3QFgnN{MefqOkd%=ofVp=<_ZY3SVCQZq^cEF+og)=QcsaS3kRk8iaeRBf z_Xg{jg`C^x&GFEFQjN1W6VGdqlFRhK(AB=FZS?E<@MtOdMTK#$PuOSKv#=3;tt1|b zX73^#<*GPPAvF2sK=a{^I=Ks`U-T{5VdkjMl^4v1_rozf@3n%!k9!SPf63L{|28GP zc~<%{shqC1lh60sTF030uXaBi73AIOc!YGzUn`+aae;c@QGbzrEHjA313s4Wc0NNm zdynIJmz1sY{H8jIZ`<`qVf8g%cdI<}pE>Qk@;$jDjn&2#u}3xvd7l^bJF4KxbwxNN zFm(39o9_jkYep@2<=&)TvzUTk523jPePbCL(@VAAJ4almTJbG~z+}1iqlV0;hXF?< zlj^6I>^)l;o7}VSW6tuD^?PsY^mf?Gp5&cc;Lx#$S)TWl=`uYh{Qbg{bW+%}EZN@3 zw4&V3Xao1g7!j*CGGkxXZfhdTOPZYtn8e3x_DFKN^Q=Kf`%Lc&t*PIFJPuY5Bs^T0 z!a49%t$i>vX92qAK##LicwViB8k_X^hEH}I3cAjDG8-JyopwC0Yt<`u^O$;x>38Sp z?&gsyI(bPfd(IWHs=-EGCHeA^h)Hwq2_64x?k2i(GLH8&p0~_<67QR+{V({7R&J4c zYGs&RuDB_-#(vk0#>>uiTd&&Bcwr^s>&&w)R-4W5;R-e{ndnKXV@V4=}{Yw&4J4TuO*JbFQ@s*PIP*#~4aze&%v1olE z_r;)8HYPEOq)ZcMXu9AHXUYQ{FPi(%Hn9&*8K+zF@^V((sWp~0iu0RZJX28Xz+}guW80@tR&Fn- za$bFXu=&H7a!@63(CaM=&s+42`Drj)aL0n7y++N#GmTXYHNU?Op0&B6Q~L29zs*U8 zZ&$f>cgGDJ;3M&e?YQ7=zx1>4$jav#%cnb>eZO<(R2*+Mp7-9B(VrigHbcCNVn5*TO`aq)}PfF4TL!ur`vFOznz{`y3;Sk}Q> zK(l4X%lXeT{fE=q4}^)eTJKMYNR15}Zq{gBTG}vM*jV+<%H4OH)PRWY-wSk7*t0~c zv=pZ9yg%uCw%Cq~e0d%X-@~=vCAdfm%3TxAOwwVUy`}r=QRNi#z=}45>sP;KiM{Cg z)W?%MnQh_M@99g;*m1mQEmyH=GVSwnsH| z%eT0vY%yC_=v#09YDd_)L2yG2bDnEv_iG>a`R6X~S>sy7=k{iTS3mCjY@T z3s%=gh4qO#BWyER{7vN!8M#*49?p8Xs^#^9?h)m?zR#kx)t;1iv>~u7 zjn|Oby6HBK_Y$6WM&8xHG0*cS_byv!)fz2OdFK5-CX#B4@WE%t_&;nEzgbey8gtBI z)5B?TDn~ft7oEB-knv8a#U-QT*op!x{@wWXaUP!6fxoe9t1 z?U>T)w%1;X^lfrRr_iEj+*e&R1J-GHDoIOD-N-Ls&mrEraF53hYm#$~6@ENM$MlV5 z&b|41nMtzO&aZr#X0pC<5zpZRYfPEe4JPcmGJBwoSZH*tO`KgcF=G$MC*qMQ%ex4z z_hhbRTeh*T@_Ox<>5uO1(*1jdP6~S#u}2%8Fj2`#k0V2A+SLvj%XNjDf`j#?__>ujQ{G8Tn*$U5IzVxYW6?M|jnY72Jz*b_9 z@y8_rIYJ*#$|o1uoXc4zcsn}mfrdwx<`tr*eeL6N9Pc$euVKpi^%f^A!o&*gs+mjQ zZQB&F`E~HqCWUEw8gstxDj1sSaVU5R@3Kog2QI{`N$%d9e#7+hnRYv($JCsQW-h1j z>+S+P?;ghvvqzr$NFgcmY$=xy%E{)6@3jsXdcXWm;INo>+_O7(AKoz4zGal%)!dmL z6Vx7Ie%`Lx$!~36Lq<8^{}{h>)!R7KAOhyU&!w%X`X@RrgYvSIw|Z~ z9`IaYT~1v3nUdA9ATlyKYLJV2v4VmHtBS5|Vn=si<~&E;?YDTi*J->lxOXzqRDZbz z%WC_vccW7x2lfuBm|Nm_uj6^AmJiEIhrfDxE=p&wjcudha7wh6X5Nb$^N14`oXvS# zwXf!^$!Cu>f7ufB-tWfdEM5&QI|04CjHL4tD^G?lz_*KHJn!q(Hv1&L#b?+bzm_-0 zXnEuuXASbCxs_+)FYX^M(R;GN_4I{3Z@V2x30a2c#_}({>$zk0@fndkr-<$9vz_yq z@aygpJZ}Ko+A^al%7;jTyLONSKU;sjFt12Dsq}fJ*@8oB6*3b`h_Clv%(Hz`lC`fn z|DB2O9kwYWI@^N!TF#}M=8Lh{#`&QX&&&CCH{0<8M@H7%JWg05niRkD!*XM;$&oJ8 z&QEupVlmZDv7xpxXr!z6WuV2RN=5wxPBXIl6r?U2>R!_vZfW0!uEo*o4fUDyjpd5f zzI8emcfC0q{o(pSzm7zW#Pv2V0mUTdV?~Mn&bzaBJ8VeV?>3@&qGxl91<(@9~^l47qZDO#tV)VhDOc*D%A9WNu*rC5DxUA@*gM`^{S2hVSQkJ7c7QUBys%^Rx@m8b(|yrV*AH_m#IJLiGEk=z)wXN2&1 z79SVjqT7q+67-FwUVPz&ry_^+PQQ1&DwFX*ePg)Zv7AM^9Fa+viXY#aeT130dK&xL z!dI9-|i|(^gUfE*>K{e?wX;=W{dIHm8?K75P_&B>kCxtzWr!&8htJTcP{>w*isb^EhhQG;lKfKyJ z)SEmuRZyo?CHjQMRsRlh_I*_sZO8N+fgy@-S0y{`H2@B}y*Kf^CWcX` z3#zZ(<$RYw*qm}QhFl=Ajue-?{^K;sd=A-r*CeMOZN8P)@|;`TJmgJ4t^=WKX!#UL zllmlbSli?g3O;^s;dvL7ecwCuPW#}VVO`tXZF4kuE`4cnSWvH2p7votdTFVMpLgga z*-xwj-^qR-3=0Y`rt^`c6SFfYL8qGIzuxiF#MxVe=XI-0F-wq1xu3>YrcpOJK{Vi5 z8dt5;-2R9tEsiT^hu#*nb9HYab`kY#ohA)`R?zix>39&c^@LT?l;j(g0ZJ)2UNo1W zZ!DT#ep8*UB}&Tn&tk1JdZpAaCNn85Xqv)|VxF~|1nv>D}TT3T}Jh@o9dtSIGyWRQPKF>|A1-Zuj!{;8~z45sHHot9l*`7$juHd~g ztxvb(c<(JbkZf_%=ckl4pYF#xC9rE%Gx*owpVVe0?Huq&$ zJP-}lamo8SF!1G=kQKRMnwR;yYUutt7_Z^z(CsqY_$H1Ds(@iyUkUxhwk zzLm=FdVWFog{V1aeHHiazBl^iLdwzOdsLq`$>wtKB?`+0JX-ReO(rc$up?u6%hv0g zAJu5P-S}>(?W6q|$9osgTOHobRnno!cZGf43K{8#9#PBe#CyxD3zu*a-q_t9S$vc5 zCc$j1Q%}SH#M17wy(>N$eUjZFRxUv}wok0XAr`+LLf6vh8_Q+olr>cww~@!n>q{jS zT{Kb_+>zE_X5M-C^`SJo)zR|?IUCwd+P=L|66~FOB3zSoSz7?U zUEHUW!k*ZCT8%%jC4V-n{Np7QAv&JIC?3&u@QY!kX}y=^M+9+f2)&Q>0FQd((FHmdn(Dms+nw zKh0i~<7%bWlVc^s_GF>?E{}v{vC^ET5- zVb2nvSZY@wH)m zv_s2$2lM=Q5%&AvcgyTx#qqY_dC%4P7F_j^oMuomJ8|RIfEtIZqwaB~5oxXc_lq)Y z*>075?qKG4dFbFVmYw7^kB+U0xtP^RG&Tr-m}jFFck+-vj`ty+H}6E~GTrXP*+gwKcPHyVH#lUYj+ga}15!XWTOu7zwJb3l`v6X_gQ(Qq{%&&19w? zd2Jky_Ys~q^mu?zURy;Jt8Y`MjQ{&dhLf)xT@w}{T=?aJkPc;Lt<@}b%Zy!mT;KbN z8!ia#mow^@3TIE^=M`om9ld^a)iNA!E1tKmJNUNv+o6#f(+jhVtF(9%_^c!1*L*%C z)FW|cN?UEy*6jV=L#s&BR>gX3w~>|g%ir56C^b!giwMt=u@aI9Dm%U2+VH&21{q~f z`~5rA4U)wV+&ZzeZ?fl%#Qn}X^^;AXmifC5>Z{juxV&6Aog6G*$Z=t=Q=V+jB3VuE z&&&tMjFn~;`r~-f8j-%SZ}j`?%H0;mWj8=oV2${>6Zf$-QEs5DePGunP-TvRFRyVcG_v7 zHiz#yC=4)SYx~sH%>&6Ys&X$xMvYE!ZHY+Ay@@8yh^nIJd zmAi4gsIQ=JES_ml#h|=WQo6-dP3{?g@V)XVRW9)Z7A6}c9wl%eJ)XKxY)X?(+)61n^6R-0Mw7CkGnpWOZ? zTW}#~dtKld<>t+48*uh^;d$A*hKdgOy-xYY?eejh`~A7fc{9_O=b3vvd;E@*^LX9h zpn{6jy*ZCBP5O{qz}3^T$V_(6a=Bj4-Ltou-&9*$g<_WOhi*J?O}WMCWdDmJ?Xw1B zUYYERvTFC-K1VGfS3pR8`>BezKALAA9;?`RJY&myg=S5yXDT!K&TqXPA9OO>FoiFF zw(%q!Zx5bV@k?%i+js3Ns*l5iW;A^MM7dbN|MK+CheXW{{M@BngN^|o-#wdJu`y4P zEn%2F zIA7Sh?X2yB(*iP{a2Ayj>%c^;MAM!FAONXx(C*8-p6m-cZ26T*Q88$ z?OGMyno7Ux_#CcDY}LCLnRh(omZ#5^a{L&C<9&hW z^|rp0uI6YH9WpTZq$%NesGyTU(-HHc$IU*!37*e_uWhQFOS(K zcWvmdyT61tVbex<)8N}FjyJ};19FnST2x8z54~KI7l^a>6`pra^rppum$}{uC{t8# z&3<{}{Rv8aq>q^(XI{~Ue$DllV(v2!q#obZpEN?)XxHBG+4lL}vz<*_dM4X(e|Je! zT!Q26!}EqZC~ERat>%9wc0Z2iTxT42vex1)t~nE#mr*<8ta2-V}4r+?chexbcjP>NyS+1N3&>kLR5xHX>T3 z@YeEeA>UWGrjhF|mb)91&q?aNFnc2MW<#>_fWKQ^z4wf?6-R3%ZMkQrC73Q)q8n!8 zX7v4(!8}$s{5Uj#=jD;(ud42y!&-FDE@ertyN)`a)5&G;2R5q&CS^U^KKELrx=VO3 zf5d~x^`dKf=3Y9<{OsmJ9;XreXtK(VSt~c=`}H??URSSvv+@epDlLv4s%`Cuc4Gy3rt> z*Qwsw%2MP&j8wFWryq2@E+^MU5?pNd@pJz6)X9-cHaC?-zBzN@ z{TU789OtmeGCSSed-a_6%t+VKcBl(J56=Q(9^y>^-2US0h9Z4}R|vTl;sVIgyK?Jv8|mEGQXIrX-U zm(O%#lOFy2@>iLD7CD|?O9$P2ms@}Bl9e1Rz%nm9J5-yYhBDx>G(Me&?q7(CxKiQ?-EY$0Bd(<<|w+EI@ zx6mXW*!tN(HiN4|M5wL!&7D(HZST&je=H9?PJC=$C~2j_Z`6{<`l;oa-pmpl?{_>e z|I|x&8*L69RAirP>$Kzhi0Xqk6(iSMPTO?_14Clb{qB#nEV+mL^d?}^&nnLlcCCN;0vzp=`EZ$pc(LZ(9t`%!s zRF^atY&_6bx8M%LPwr;^2+9D&OrAA9Q?JhR9PpjwcDDP zR2NiSpP+XBOUM_~S+`D9$;N)3ZWq~>6H{Dt@Oy=f;pRe9Ki{U>lB(qUk)+p&(-*9* z@Z%Yc_VUH?GU0hcws+4Gn*urqU%y`~|*4OPsqezCLy7$cs$BxX78hBN- zy-q)^y+b`kQmpa)`Lqhog=-Uy+&R}(Zs;0VW41rG!X&PE?%PDJTAuT_@Z-fKJnzw3 zwOzfRytXw8EcI9%k0zp_K5{Huj|!h{x&Ary(dtXL^eNYKb4qwQ z({^+6M3n{#3bRc|RCNEc;CUZ)1p2P)_FQ-_^1!saRZb_mtzJb=>Gz1=KiTEhsF{cJ z{`ICep6*+w(=Vx*SiGOmUNHAih*Tw8(8tLu<%<*|0&u*nc-|+iFXTpA8tbhd2pYED z`F6CmVmR>q3G*JJ#iiGxS+>z3mDY5#$E^e)pG z94{N5cZw%ZU$1UJN72Z3ix8e@fhDK<4?Gy#C1p10<)Oh+3-$UVO&cFwJ5=X-r%y#N zJ#GHmI+Kx4O=h<2eG7sY`=+96hjc%%<9SnqcXY}KsSG?2%30cL#m&c;yo_%*i^ABa z87vD@?>dPFwdd_HnQ?m3dBVq3F5CA~Z(Wa>R8=2RU2dqW?Q}U`2gf@Z&+8n1?b*G{ zNs|;RUJvC`Dw(e@ z(j9WQ-FnW=kf|)};wGD2>0KPKBHO0ri7i?a)BP|7&%5P;*48tJ>oz8tD+C5?ADB$s z=9MjCKey@it%{p*qD8F{8Av^(H5t zcj!Vh-y{yF&C&U{nhjMX?%!Xth;3~ElS!6P*{Za;(mF3-8O+UT&9T#ACeJ+GI-5HO zH6}}Rl|B7vn11E%Uh4%od#B=gk8Q{n&(8!+n{-8y5cN&j@+eP+ zWz*xHBbzQu3FeKNFD&Qx(}Zu}-aT17SuCHltmXWTC;7TK-f4K=sr9A2LM%=`#hz|2 zKe;(vpVskw+debLa^2({*)!D#^QBAgDGQt^^2oawxN~jY6R~nz?RicvPc+OZr}xDA+nuAH`p<*eADcV8x#vF*JQZseFe%l@%fGvRBK z%z{q`P1aI&`Y9-@FuU!MSb0uBUxOSR< zf5jB$FS2K4R|#<~XgpuE-|^e-HF5fWBjU;5_X)_kSA9siy>YOjtqI4=gXcZlr!Vxv z($?jw(#=uU{DAc1L6)PHho3D!^+tlLJNwSUFR6=3-yS`(7)T14wtM{s?Rh)oljrL1 zC_UkozrEFD|0x_VFP`^x%9ou62VY1kG}nu69#-?qQynm$7u#P*iTCr|QB~5@x9ys_ zVNcK3=*VkQS2Rt(KXS>-=t`88%*r8DnziboXDR6M%ZKNEy6pHyvjtJrL;K~|<}Ywo zvXd{7FmccvxDr)uVleu!&(pJfTA7t*SM}1Tlqq`iEk0CS)IPF{r(ae2 z$Z)oX^n8;*^cc(32Gpc-C(c(u;8xD zdyVok94|I_z;Cp%i27d2eD=srBC)S-Mbt5l&0*nu@wtSg_i7LObYHP0MXXBYdH=0C z;?$VJx9afK7p~-%y=vMny33fwkmZ{2>Kb%Uh+c0(czf>`4vk*A+rZj++Boy|G+DtU ztqu0m*z!J_CX9V~)7;WwQN;fA@Z00xPwwKjdfh5alI3$$t9~&&nP@J*=ZvW$o>v&p z`~I^15%!t(AtKwZ*qz;c^$p?UwLKReDr{aiy)MvcVMFq%7@3u;#bp&E=HAljXV0wC zj(V)}qUCK(uHM36;eC@f;q0A(=e3Wzxbma3(d?PeQ^mUi)yUTJy@m&Jnv}G~t;-dd zH}B2jmFB-5wfMWvswccfHd|Izct<`H){}pBwP0zt`OtH8Z-MRy5j=0`2GZNBi4hlT z5*M&*Ju!c=ev|L^iowp!(?;&SaxlC3Id$^zw!3R5)kj9X^jW<+g1Dto*-g5AjYQ1} zw&K^akI}U)I`2$8?~50WZ^xzzG?h-0UC1p`(Uvwt;N9hOto!clJ5uy~X=#9ipi=trI@!+i|G|;S);7eMQ)p>xa|`aGy3hIoYPlBP(0Lf4?^aVCZ5-LzuX zC#lDpBTx6GeOht%*uIzR*BOZV=&^>S5jL$~^`R*(X6x;kdXL!Iby8>22hOhUNS9FD zwOQd<>O<>!f}*R?vlMh*X*_TF{aIP}HJ-Hxo-%yk?lIqI#rKt)Gc2l-3_r<>ItVz~ zjoAw?X=e_9-__9SD{w&CX;J5AUcE^w3&q1o_N7zil;U`2<9SWO50P6hdW&k_3KTXa zR6bBNGQ4y@u`t+Eskj?p=l- z@$+>VJg;-$#8n2jNo#MQeqr6a2FfY1)=ff~3(dLfTD=l5LhMljrSc>_Viwd0T zk~3~yU0<4(bX>)W-9|7@K=J%koV~Jm-o+c+hSo?dP;NVHuGQbnb-&j`_4%klYTcD9 zF3Rp7!Xk``&G9{TOv{VK((ZVN2!1^uIcI1rXz1hF`SR{!BSON!JF)$953tZqrSBPO zGJ_y6m_(s?lY;NS%~NgyA$GiT(Rn1YueU!;5YMp@2-6sxH=gc9+CTRI^0NdJga;-(FyVp!7d(LGiJ##DC|qKQ^>d`o9sggj`+vhm+@<*c4f+Yr2@gzoV8R0v z9+>dJga;-(FyVm-4@`Jq!UGc?nDD@a2PQl);eiPcOn6|z0}~#Y@W6xzCOk0Vfe8;x zcwoW<6CRlGz=Q`TJTT#b2@gzoV8R0v9+>dJga;-(FyVm-4@`Jq!UGc?nDD@a2PQl) z;eiPcOn6|z0}~#Y@W6xz{?B<}8SNW{XKCN|o1#Y!c2oBDCsT;NzRJD=zL|G?hH7-O-oaurHR0AbRObR zrKQb;V{{%$Q=_G!Z@8lG zH+meQ7(w;K35inyDE_zr+yDhgR|LRkXbAAx8G;Hx6`%%C2WS8^0a}2003E=5zybjJ zUbh}VA7B751Q-E~0Sf_(049LN08_vcfEmCXU;$VPumo5EtN}KFWq?@#aexFs5+DVT z2FwP)r<4e?fH?pZ)6=00GXNrh82Ft5$OP;M>;~)s>;xnNb^*2mwgF-Rn*f^uQGjSb z7$6+r32+6t0bBs<0L}nMz$$<0mF0q+1qfcJo5zz4ubzzARz z@Coo4@D=b4@CtAma0YoV}Rp;y?_)zG9U?%0Eh>~0k#0V0Nwx}fIDCfU@f2@csc-&0S^H!fFppTfMbAW zz;VE7z!|_fKo%eykOMdmxB$2a*b7Jj>;vop^aKA>KoKAxzz;y3&L@yvgToVmeSqzN z5Wso>2`~V1TfhncGXVX41{MJNI|AsN|L8mV=-c$D??ini>KjpCh`yhVzDbSxK-8C^ zeo6v>`XSWspneARE2tkqO%L@Gs9#tBxD2=ga03*B4JCk5z-RdV1>g(EegJMFdLX?O&;n=#e1|kK*rTxqjVR0#xyns*unk69HP&}jhMe%~-2E{gN zdu{+%0BU>m{>284tpQemr2sR)62M}B31AUG8=wkM0Vn|!0SW+lz+Au_fDB+ZUn0D81zv^NHr0xSUL091B#YzaW=2*?M^04o720JZ=- zfIVO}0N;17f#1kqM1Tvx8L$rE0dNP90NwykfEU0Qfc%f_Lv@0Vk?)a@k$(dL$gfBq z00;)4woe8iTTxx_1)K-u0I~q6-p&Ee08Rr=0b&3f0UH33fN%iPjQ~Ueq5(*r3D^XP z1snyW0JZ=&0}=q+09yfZfOxz08_6V0~dtTV*(T-8$9E0ONz<0nm zz*oR$z$d^c;57i{)eCqAK>mIT$Oj-^4nQs7Dxe3@4Y&?K>0N+Mz+(Ulpaakjm6;K3t1ZV_21e5`809pW(0Cj+7z&$_{;0~YxP!G5br~%vp+yqnuiUHRE zbejs{80B98KzXD5kgZj;-zBiG1XKXZ0Vw}c06G`xAfWTn9_gTC#EESDIUiI$lukbf z{~P&(E~EQz8l)k=pfVyIdiu}DNEi7BrPJ*}X~@RAv~;9Jbv1H~xvKZ>D20E&nAfFZy;z*_*~MS0Qd4ee3BUjX<#U(wQ- zppBw=7JmFfYeckmMDr;$uVMqBc~~Uqpf+g-x#rh-{@~A&4oQWhaHa5e2p89 z(cBEp)6l$40e*AB9?j#t;n)p;=5Z(ut)OZZuCsHp{?X3U_V-ZJtuI9t5yW(Q{JQ zUK^qVR}QrQTC=JBxN^XQ&LI$DkIKXQhN3$~O zrU5&spV+8=&xQoP*HV+2eTftjneyXn_Ju2Aqb~BC9i7bNtfQ=@4Rm^-BM3U9p+%>T zdBt&pL`_*+S)DpV!dn0mEoBX5$YeP!pBWdoiQlzLSj)`htgSp>Sxt=)M5bCI6u!m7 zFXMd!CQ(xZx_A&kpB6i9`snJniCr5&q6X#B{40`U4V{NVKQB6(Lz6&BQC|#wLu~&= z{bK_8_H$_xunk2X7f2RIEIf3*V(~OgG9P(oBe)mo98Rkh3Gyr8#3aZ7!cLGN?}&+h zi%HIUaht}bMqP72yNhK{Pp+5V(tDgHfs%R=$pK;hQ0s;}IksDLHGxE(S__QxVJsQ9 zJnG6|!7Zpmez4%}wS+xslb`F*tOm(k7*GU2(kxouVRxnag0h=(e9p_3d4B2(vh$dm=Yjxzear03_aWJj~E6=~NKm;tF0k+ZO8f?>2 zRs(nc^?b)Ug(Q$sCS9VDoyn9jON%vx*%iOqO#)UEeL^96V5GCz$7R%EVGI(mSsjdX^CJ4L2T9}8O{$jD*E|9VtxnT{4Ym4Z z8-0sAE?q_zpgM(!cPF`qc!EyS7J_ECNbOBbM+1`txKV<={Yg9QHzue z46vd4NjjEi|7}B~{f~UEg9P<_htp=4TBV#B#`3{h)-8~rR{!EcM6yFq&s{3AkU)$*A2@)6-P(B{s{_aX4 zfdsBzZ!VGi9hm0KofaqDd?a&_5U<~)XK+p zCdP)1nhqdAql`FnkMr_3Zvrt3u<^$QB&Zeyc`jX#-0hzV5}FSJK!VzjM5sk>uz*E5 zmd|`-bp%Z!Ceg6p1KCwSQ;Zula1G<-@of z5Q9AfiNRzNGxwY3m6e^SKR{N4$O5R4&XkPqwm9~u8khvSH@JQS*+XGtEBJIjth3x( z2NGzDs5vr-Uht?QxCTZ1%H?&|M4l_RKnK+}cyu03vLu@Sn5#3^_tGLhPzi2C!7T5g zv>yA`i1=-wLu;oX@%Hnk5I(G3miUcaD-ROX7NDIf^3qz?8H4iPt*&S;hH4w~DFF#8 z&n>xiv0>dF+%z3%a~?32q>$VRcQc?kWdFr zg15g1+*b3V2%b``eBIOZfF?oxfxj0i*qcJ;J=7;8{Nw@jVvq&4F=&lYKLP%3(Cffj zJa=JQYB+HU#)gfj!C(QnJN8ChU;f5*mO(!(2n!|#21dY|J-zkLoaoRm%uGOpZH(C% z>r92=0@b#k+$`6ueXcglOsIMgqdrV1P%W(2-&R~RqReM# z+}44aH%P954vM$beMZ#}3LJ$o9c;{EFv>tiKeO>LnZc9^&5b%?kJyNnk~fy5&Oogm z#wC=`xVB;a4r2-9k;#ut_20=W)y?@2cbh@YK|xXN1rjM>ro3_xGt5lAB&ZKrT;=B0Fz0N*!&IvWtMdot zsP&@-p7bD+DM7Fg?XkG8YP}2Tpmify5KIaodwWC>xMwe3VYpv$4JN@>df$MJ2W;Ev zzVA|T+wCWaO;s7OO@pZ*8c*Zd<&IdL$UB4SsG;6v6-ZER>x!E>NA9&)ib*i5Q^aYr z=!E6RKdtkn&!VBE=pS``ypDe|T4T^gnG%qo{^0dgi5Z-mWzc*SbyiT)aeEi6r}%aCaTqR$pw+-C zY3$laZ11wjNmll0*MJS$j}{EiZr)1J%?A*SCvkrD>A%2*<%6+Bfex~5`G+g>&)#T5 zwn2m<3*u>#Cuu|Kr(;f|xeLuU26JW5NdXp^sH3T}el4gR07bJGCr)#f_ z@J2l!t>5tta3_+XdnxY;)z6r3jm95nSFqevy97GOI~+@+3x`k5%LNH-omB)9)Q8wR z)GsIXZ-o90TVp^i(EkQd`s&@vujMbJlAmk03jnnvd_)>me7-nowUw z^XPeypz^$}-Vm7nu>L+sX!DQ=c=E*?E+`Eh%xw{gmc;q(*Ya#c3Vsnwj?mxv2MV~h+Th5GnF<9myoedy?VJr5F zo%4*=&K;eY1m;3e+aN(6_0n2*T19bSKb8*`JLBeq^-dYn;&xdTXTf>u81w3X?&?xUF6tx^mTV*66hIG|E#W z@#Wba+$}9^Xe~o)rwt0!abfkG3hhmMR@?&#Y9%mg(*Ii0T(wh;Ghc?FRW+D^s{7aW z(*Z2V$NjKJS7Z*q<HYx znDRr?4iYqGu^amCD3U032MMwbboxMo@{_HbU0Bxj5{+3jk1}RstYfZBorj$82@Q$( z?j%Od2UVQ|NKnLY$t5+)&$mJAMpWCd{vm<{2F}>8{wa#(YCA2lh zf3z;4wfb?^B@hJ2JAq0RxUvEFFdY+v;RM7Sx++C^*!aHPzXbIZG>?v_ z15Fq@-=DAZ@h}mP^C-4LmIDbY&w#O$qn3@pe5^d! ze8-5UbGd}P-RICMGzNpG(Um!3I7tcq$R+7+*oiSgE{RHJCVfq1=r7^f1e%WMXe{TI zvYbs=K3M;@1|(>P@`~-wYa#w<)Pj-UfGq$dXw>X?Uv2+buiOMA^C3c@e~YB)2(D~+ zswiFUOw&=*`puR4U7&-;`W5Ywa}`8&P*kDV0Y4w2Np>pS6*;M8gGNnc0n9oX&#TUW z4)XQLSy#)rR`FO&2lHD#NcccMQ8hvmNMLakYjWf3e8ak+ z9hd}b3#UPXW?pY&>z2vOemX>xKt2^9nGTYGw78ciXO+{hhk$n&*anh^po2=vWpto# zW|W;Hu%VJd8+`>5)J_N7pS}8+5M@cV4M|2pA_Nj=>n^)j%jX(n5^Mxya2Ek=XsiF`&clEA&X5Y&hT=`Zw(#gO*+_Gm zZQwUOkRb2)9V(VgCYC}g!Fqov=~9rOI^}E~-tTyolX}fT9mUQ{knn({!$Bt}NvZHL z=pbK%4qQhHCV7&=ubG}muTj*p!1BRHKo8JCeiPnWlUC|@4$Tu$T!T(DNKm{9JV;zr zdb)0aCV_mmf&|5NRFy!NbX0SxKSR(am>(0`q1HcAX z$W2>)LUAWa8sV_-Gd#@M5b zC5-*XScic}!3T`l7)J<$o&utSQH-kn=P?T2VQc~8{+fYpYO4RLr2pM~7>sjJ3;)LI ze=|QA_gMmL+-Ce-0OOvVaqRpX+y4Jz8@Cp)9*OaI%Gfr>ZH}?dxE5eDVg@~h1{&*$ z!NJ4`0)OX*violH`Ji_}vjFIK80W*d7P^e7Jzp+on_u|WtLXX;VuPNKafIkDq+VAJ z{+|80r6-Yo?+|Qb96Jnb1FIR>25o_{5B^OgGOz%87se6>^#k5v+)f$jKt7CD)$uSD znF^!KuTMmc>zzN0n&Vmk@I&MA?3BOxoFpdKTV?B(Tev7H|L{ZaCp)VgXi_^wN zu#Lf}`P;E^+>&CeL&mLR)Y^1uL>B?;~Cq=fbG8?2TPVvXVGet z@+w}g+lJO9Xa=dSN?>qhK^vag1|0#2cptANqhVdT)KwdH<&^QNfXj@wni2|3Sz{OR z28$hR-N@Jn|E7NayKLvdYJMoGgALt_PF>3+ z$Z`-a9+5`ZcW5h?|JXK&H^$c~7+*hN@a!b8F)oh`tm($T7QlF9Vmz~BJpM3V*B5}_ z_@K6pm6^SKJx4@oF$%pH&#SeWigR%a<-XHv% z`iTXrk>6@e${MfjHn67oO@rapBA7Wa?&CXwjThLWMuwCs)}6*(J!UZeAU4oJt9Rv8 zNx42_DRec0wr*rwN?lQou-^`K$VcZ{#8wc1tgmKL=W@D_wxGlJofW+6!6nIAMM|4*Lpy#*{hz{2?XUa{r0Hup+1CnUEMU717oH6eCXK!UE! z9SJQ~^1jC!j7ea0Rww9zL;@uKv)-3AKbj491hka3v3cSekRYp*+s&Es?z+VPkWfGZ zw~=Gdi9N1q&RY!EQ0O}2JP(VWZc<}-e;Y=oU$3D5t0$Gvl}5;ivDLx0)VpKM9owQ) zw`=(Su$sYBAkf32XFXga;A!DUE-_;MW7w5B$OdW-+T3{0sX`?FdM6TUK?7Zbq|lz? zJxtP->iD#>6lwuxRfvs3_Vsp!HG}EV$63dPu8)F54Q}K^d7#Fz7JT%tcSUNT9NWSB zg?7|yX?LEL1g0i^fG2LCZ6h**yLT{V9J%^Nd)@v)6p6Gup4Fg(u9&GAo|K8+!Ye|x z07VGnXIK14!JZ@-?}ktFr8DpK*n`;y-iDI;Vh>Fa1~NDVvw>K z!4u%%w{f05MzI4r<63~-A@o~8_0DU*>cfsNmZK*z(LH3SIoDvK8;J~0bO=0RE2?^d z)+NXXz;+pQkPqx#`o})+A)+gQ@P+^?kGwsVP5*uQrCJ^>W)Zu9$ zq4widJe?n};$b`#vKzK9{~~ZIc;6lYS8sn6l*Zk8Lhp@OP!WYpCIyF*g4f=c*ktRm zbkv1~>YF$nt$L<#uI~vnJLaR0F2DxgD}Z^KoZ$4 z*gKG7^y5&8+Co&Yr_+O7L;RI688uZY&^N@>+g~YEBixthN~Wr*1d+WcltA(V6&1L7 z2#<0FM=1LT`uUKR1A;w4sGfm)0#~{@E<3C3-I^w_Jm3yQ7GOdc&N`8x(s@s<^yjv|CB-w zS0bvZMf^P#v^_|{N^tWeAnX^(IJ{(Uzd&D-68swwOi?1kKY!OE2lx|%eaL@L4Dp9v zEZG0JycoyJ54Z~s*iWuM;#BE7ueC;B1uAn1u*aLsQj>UX*S;`0!Hym3EzC>lv{ z2>8G)0Gt5vG0t%Fx4cjwQ4@Y&FzPu!OrgFaib`cfYo_Yxk zqzPyz2fNXK`;d*iLi~vSVO{~~r3SJJEt0T1Xg`Vk0^Ctqsr3zI^#=u5WvO_OmBZk@ zQKJxlPhy}7jFKU4l#pPkPdDEHf0A2p02xMtKw=nFx*IW&qy%Tc1cLfJ4vf=SsFK~F zH=-SfdXv2=@XQ6g&_YIUPCyp}FWg}lD$q9sl}IoeMku+#6B#5lc~gP`9g;lZeIqXt ztZcmfLW1Fc=-0ep#elsUgT_vGk`DwVWKMJi4yq0C=7mz2H^nO;gaU7S1o*>m>Vxab z*b2$d+uz%dNOAM>CqjXUzF0LwW&wd{xFdp%h$c{p2=o+EI7JEL{!QHdI0>ru#}6e~ zM*VTr`;iss{`djY4_G##$Us(5o%$OKew+j>KYlJ2i$a38(iqRQ zem)hl|2YY3mfFaO-|(PUEkPIUz-V-RjC}0j+M}?6C1g( z>k05?Kp=|3fZtXBPiF$%Pbpvy{6`(BiM~X5S_WSR&J14nL|1u#6BR#B0_Gn- zz*B#267U8$E<0q|-x9zu)UsxO}mgpRmO%8 z6wR0xh@c}!X}|IKkCUhrG!3vH1>zs_{xLcs6=>3qumKM96!i^oY+iuP4M^dzu>b8O6TMtZJq!47IuPf7UaaD>18v+d ztQ>!;JNgUjC}Y~Y>px2J8^_aL8=#;!gpD!VfgCD1_HT zsGS2$bm1*LUlan=X(d?+t`!CNhERUz%AZdK+Mknt^t!)s0eWp7bkXh~wO;DW^q@gK z!1DN0vFRTN04@3<)+_&M#z6nv0H#O%@&K0KpXMhxkAf=h7pDEEFhd_x05!COwg^{Q zQJ;bhbFLvCu$29cGjV4D1MU|V9e?V?Q2@gC9gxY?BP<%!YlkY6*bP95M%oPnmf1UHgY2jzskM< z?oWUI`IGEaKP#l$$#!rTL5Ed+AAvD8uIbF_^gC5chvAJaJK}1%oQKC-)ITYYP*Rvl zVd7A~Nm_?;fh^DJt2Vr+^y(8BIjA`m@geJx5!riE;yhY(4kno#E)u(o`_=Su;pHIxSja(D&Tv7 zR*JPT`Qa6)a**bTtynGAz-T2S+{S#|Zv;^9e}vcgRD4H=3NV{=rB$;&+GXOH;0bXE$h-|wlOzuP!6^<#g7Nl)BBltPdtBWI_e23yX3;!({ht67y3 zGw>P9E(b^Kr(G*-nay}BsHV2nF==zyvteAfm#-auXE{Nfr3f^VPv&(=Ri;*&`KoV+ ziN3-fu$c;9JrOO@Gf-AAGd0OT0Ad-nvmrx5m7Gx}^YcY3#QGXkWRzCL)g&SJE!)l#q^|@;i zQ;oP0{_Pu;G2hW(8CwGRkl4uZ23i@_h9<<;k-{DoqQFkmC#=f0I2BPPGwId{`K3WP z*_?QVGc^g6s@(cirDQ(gEDAdX;7J6`wCn3^BohV8Mo<*7mN zTp>%gvt2g3+y3o}Vb@0>1+qZ%6JYsOBVJE+OC{0+cEIiP;Fvxs9IVq`Wj3l+Mj0ik zb1_0}LZm4ZI8$UAu67nk9#LJ}wC6Eqh65rW;%8D&yq#~Mx@*h>{TN=QF-(t6L6<_S zrs~w-#_-tsTI@6stky*bb-JR+1y&K0=J4~c!89udgvkBTD~5*-YSC&xDw&}j!xYS6 zd}X<1p?Y7-=#paykMABdYLPDmPnvcri``|AQMuDrBa1V3W1(t-hjfad%1BaS znz?4wXPrmr*XHCo&FH$>;VQzg)B{~+gC9_4@`^D>hTFkRvUmO77J1=dktS&;I>2{X z0=4^{u^G$$`S~%xauCi;X-uzT#*q}Wzx3R!id>)+F^1I&Q?4(dQ#XJyMeIz<<2o8* zX{F`SRz2L>>45sotjE8C!Cjw(VWCc7)FfSCr__8NhB^nQrL{x%5&e=K3?dn~I9$U@ z5rdn!KdRHT`l38t+p}zuh~7I>N3?l6&7Ev*nZrI7k_vlsJ$;p9#NAnlDC>E^7{ja9 z`pm@rJ>A&4|J4{pGa1QC?PU?@_6MeO9)l;`)t)vl7_IDi%g?5S{hR&wcl+;`B!mnb zJaNZ$_-+44bM;gqJu;Z;ebz8n$6UTWeP@L{qhGx$jQEIHZ2k*&!c}KQ znM+o@h3X5s3n>`n-g_eeq3-wC*<7b#oYI)BEb` z!XC3(3E8H!mgwh@hHfspFFy`^rJ6WCb_pm76M zM^o;4K*aN;?qQf2 zZw<8CVf+Yo@}uc+Rb%5CpnD1n-24HRh>Z+JWRhx8<6rlOVSCURYC^Z=Fp)WVn6kNA27l(a7FLvG!Ic7@Ol=4Ml?ib0qUw)DE!9Vo>uloO=opwvm z1sH9%Ok~~Q@kVQ}bV(-N0)ibo|L;(oZM*l%id^cHk;ZyAb+0$MyWnhgKe(6YcbSy# z$we2Sdw3kS@7q21?wGUI4Nrb9H?XF+dZNg7exp9J%(_{riwFOBHZt-?V#b ze)Vj;orHw$K8$#yUo-=`$+)?^ovv&^CBr8CppZ6R2p)%*trfIEQ+XnOdBy!dvBzF! zPDYk0_6QWr6Z>P>er0l`!DP1?Qip6gF}xgIHCC{0hUbfvZ_beWA{W{?`*}hytDvIn zLo;zWJ~h+10dc+-oThqIDO0B{l26aibV`}*y?Z@%m(^448w5-Cq+ zodnn{MJVpGLL=|?+=;zjyf5fphW~jZ4Tqq&X+ltk ziifBbY@($8hqj1`Ix4rA_-XYAWSO{%6I!*P^okS-Y6m=2`VCPMXixHWY{pgbMXW{Q z-~!dx!WOE3`8cXJnK_^mS7F`X0>HfgQP|ONU&s(ZF491X)w0DN2}Tiv+Wz(Xwpk6f zzk4<0)2jM4aqoeup3%kA*=BB7ld;XVckvaCIwpjK2gq|!?G#Pvg=~?_FXat!5Vd%E zqDu@ou?d#Z?$efd0BL`K6ML_QzOOE|2R?6$yjIEbV!b@zj9o!z5%RKm#cOS2AA5(g zR(1+#CBuGv-tnRS;1VG~Rb0xVs8JaKNzjCRil`f-MUm_ciPmu%AJ$@4QnRB~NjinB z;H{L)9i~i?MkPeC!*&++4Wo>!K9Tl&IyOK)MMN73fbD~b;WSSu!i*+=9%{uAzy>X# zcfNpX&js|C(AmKXv{?!$X&GnG-~=)xQLHb46&Zo7DOKBObT5y9&YI2W;s~&6L}~D} z&!2#HzKYXmq*X(+IQ3@ebj&Ib#KMjGJFN&wMF3iz00uv(67+7Rg#e<8iwkMy$K=ax z$6?zx89jQ_>EnAOWg#;(9i7k>^ao_6MFFjfOP#BsqgF4bf+=)T17Xs;b=UGL-_)>9 zx2j!Rr32JT1~0iC*pPnRA2zq6-SaR-LvO=r@raur*=U1@`j=IfyH*z_?>zx(KZ34 zv_GhRlDX2sHo3Ujp`cTbWoAlPCaNCVU|2TQe&gFfBw)@mPb8F6CsQP8nv(o79FsCK zZw3yG(}Fcc|DOFk>&Vtb?Okd;N?|5;pgc_>R^(2lr%cvqPHStp$zjo;or;=hDOa?y zplT{ar~5#8z&lMYX9bVTws(kSy0>Dq@KfzpFFR=Ki(pqUS`Q}2tJBo95r22)G2j8fQ?!<5*zam z#HmDkTnLW@{X$%@XVzv6qps{4Y`o?P+240tdza6KJ6~wYw6S&iWwOT@SEF%8H!q_R zQCoJPPc|NRn3CvY=f}M8rLP@k8o3R1iDA}Bv)A`qY-Y0J5D(fiaf9n?8ZM1jqGT+Z zUpnp7;T{A<3yPMtM~98|*Fykxy_9JYGCW+v(!VV=YW%tn<^f|2vxCI$ zSpHZuWFAmcNiyIWjPMNF)MhZDdXf?zJYs=;L5UI`xTAd&-Dj_25xS6EW zrehv(#_%ey4I5VG$j2cX+9C!6u?;1^{tamkszJrLob7C-=rBI!0bvYlC`5Z-F+ZR! zVvK?`g*xRn*12+#06eM5yr}>T|qZjun+TA56%AzCGYhn*T7!Y6_!@;$YTW@i`@aJ zGnLA0hFDOkGooVxx6}N2N{GiW$E5V0MW^K1`+3mMQglp-1mDJ3s3tA2RlH~+z1sa3 G{O`Z; delta 1152 zcmc&zUr3Wt6u)O~y1BOY=i6{-)-S6>GkyoYWY1Nz=&RaWjyeM6Zo;yCi%@bl?Xj`#fAR3?j=jn(fRJZ@GjWi>H2 z)YGf%0|>j&o<}qyP9Yi)&o%aTUFu}41{Av$QIGy0q7HEq^9UzYj9C$l35=y6wsmzk zV~G7gzl3%c<1I@i}|RxK%xfUPg+a$TX-}#noa_n)PcjZ+eySh52Qgrw4VY8Op3cFz^-;PHYMKGft2ZX6*3lo6jh_SPZEUi^M=VO*|+_i-2963@~ = [{ - label: "ETHRegistry", arg: qs.stringify({names: ["luc.eth", "nick.eth"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"}], response_length: 2 }},{ - label: "ETHRegistry (extra)", arg: qs.stringify({names: ["luc.eth", "nick.eth", "nick.eth"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"}], response_length: 2 }},{ - label: "DNSRegistry", arg: qs.stringify({names: ["luc.computer", "antony.sh"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190"}], response_length: 2 }},{ - label: "CCIP", arg: qs.stringify({names: ["luc.willbreak.eth", "lucemans.cb.id"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b"}], response_length: 2 } -}] +import { Dataset } from '.'; -export const dataset_address_bulk: Dataset<{response: {name: string}[], response_length: number }> = [{ - label: "ETHRegistry", arg: qs.stringify({addresses: ["0x225f137127d9067788314bc7fcc1f36746a3c3B5", "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"] }, { encode: false}), expected: {response: [{name: "luc.eth"}, {name: "nick.eth"}], response_length: 2 }}, - {label: "ETHRegistry (extra)", arg: qs.stringify({addresses: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "0x2B5c7025998f88550Ef2fEce8bf87935F542c190"] }, { encode: false}), expected: {response: [{name: "antony.sh"}], response_length: 1 }}, - {label: "DNSRegistry", arg: qs.stringify({addresses: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190"] }, { encode: false}), expected: {response: [{name: "antony.sh"}], response_length: 1 }}, - // {label: "CCIP", arg: qs.stringify({names: ["luc.willbreak.eth", "lucemans.cb.id"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}], response_length: 2 }} -] +export const dataset_name_bulk: Dataset<{ + response: { address: string }[]; + response_length: number; +}> = [ + { + label: 'ETHRegistry', + arg: qs.stringify({ names: ['luc.eth', 'nick.eth'] }, { encode: false }), + expected: { + response: [ + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5' }, + ], + response_length: 2, + }, + }, + { + label: 'ETHRegistry (extra)', + arg: qs.stringify({ names: ['luc.eth', 'nick.eth', 'nick.eth'] }, { encode: false }), + expected: { + response: [ + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5' }, + ], + response_length: 2, + }, + }, + { + label: 'DNSRegistry', + arg: qs.stringify({ names: ['luc.computer', 'antony.sh'] }, { encode: false }), + expected: { + response: [ + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { address: '0x2B5c7025998f88550Ef2fEce8bf87935f542C190' }, + ], + response_length: 2, + }, + }, + { + label: 'CCIP', + arg: qs.stringify({ names: ['luc.willbreak.eth', 'lucemans.cb.id'] }, { encode: false }), + expected: { + response: [ + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { address: '0x4e7abb71BEe38011c54c30D0130c0c71Da09222b' }, + ], + response_length: 2, + }, + }, +]; -export const dataset_universal_bulk: Dataset<{response: ({address: string} | {name: string})[], response_length: number }> = [{ - label: "ETHRegistry", arg: qs.stringify({queries: ["luc.eth", "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"] }, { encode: false}), expected: {response: [{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {name: "nick.eth"}], response_length: 2 }},{ - label: "DNSRegistry", arg: qs.stringify({queries: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "antony.sh"] }, { encode: false}), expected: {response: [{name: "antony.sh"}, {address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190"}], response_length: 2 }},{ - label: "Mixed", arg: qs.stringify({queries: ["0x2B5c7025998f88550Ef2fEce8bf87935f542C190", "luc.eth", "luc.willbreak.eth"] }, { encode: false}), expected: {response: [{name: "antony.sh"},{address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}, {address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5"}], response_length: 3 } -}] \ No newline at end of file +export const dataset_address_bulk: Dataset<{ + response: { name: string }[]; + response_length: number; +}> = [ + { + label: 'ETHRegistry', + arg: qs.stringify( + { + addresses: [ + '0x225f137127d9067788314bc7fcc1f36746a3c3B5', + '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5', + ], + }, + { encode: false } + ), + expected: { + response: [{ name: 'luc.eth' }, { name: 'nick.eth' }], + response_length: 2, + }, + }, + { + label: 'ETHRegistry (extra)', + arg: qs.stringify( + { + addresses: [ + '0x2B5c7025998f88550Ef2fEce8bf87935f542C190', + '0x2B5c7025998f88550Ef2fEce8bf87935F542c190', + ], + }, + { encode: false } + ), + expected: { response: [{ name: 'antony.sh' }], response_length: 1 }, + }, + { + label: 'DNSRegistry', + arg: qs.stringify( + { addresses: ['0x2B5c7025998f88550Ef2fEce8bf87935f542C190'] }, + { encode: false } + ), + expected: { response: [{ name: 'antony.sh' }], response_length: 1 }, + }, + // { + // label: 'CCIP', + // arg: qs.stringify( + // { names: ['luc.willbreak.eth', 'lucemans.cb.id'] }, + // { encode: false } + // ), + // expected: { + // response: [ + // { name: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + // { name: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + // ], + // response_length: 2, + // }, + // }, +]; + +export const dataset_universal_bulk: Dataset<{ + response: ({ address: string } | { name: string })[]; + response_length: number; +}> = [ + { + label: 'ETHRegistry', + arg: qs.stringify( + { + queries: ['luc.eth', '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5'], + }, + { encode: false } + ), + expected: { + response: [ + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { name: 'nick.eth' }, + ], + response_length: 2, + }, + }, + { + label: 'DNSRegistry', + arg: qs.stringify( + { + queries: ['0x2B5c7025998f88550Ef2fEce8bf87935f542C190', 'antony.sh'], + }, + { encode: false } + ), + expected: { + response: [ + { name: 'antony.sh' }, + { address: '0x2B5c7025998f88550Ef2fEce8bf87935f542C190' }, + ], + response_length: 2, + }, + }, + { + label: 'Mixed', + arg: qs.stringify( + { + queries: [ + '0x2B5c7025998f88550Ef2fEce8bf87935f542C190', + 'luc.eth', + 'luc.willbreak.eth', + ], + }, + { encode: false } + ), + expected: { + response: [ + { name: 'antony.sh' }, + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + ], + response_length: 3, + }, + }, +]; diff --git a/test/data/index.ts b/test/data/index.ts index 00b30c0..e38c7af 100644 --- a/test/data/index.ts +++ b/test/data/index.ts @@ -1,10 +1,10 @@ -export * from "./single" -export * from "./bulk" +export * from './bulk'; +export * from './single'; export type DatasetEntry = { - label: string, - arg: string, - expected: T -} + label: string; + arg: string; + expected: T; +}; export type Dataset = DatasetEntry[]; diff --git a/test/data/single.ts b/test/data/single.ts index 9d4bcd9..bee8580 100644 --- a/test/data/single.ts +++ b/test/data/single.ts @@ -1,31 +1,103 @@ -import { Dataset } from "."; +import { Dataset } from '.'; export const dataset_name_single: Dataset<{ address: string }> = [ - { label: "ETHRegistry", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "ETHRegistry", arg: "nick.eth", expected: { address: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5" } }, - { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "DNSRegistry", arg: "antony.sh", expected: { address: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190" } }, - { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "CCIP Coinbase", arg: "lucemans.cb.id", expected: { address: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b" } }, + { + label: 'ETHRegistry', + arg: 'luc.eth', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, + { + label: 'ETHRegistry', + arg: 'nick.eth', + expected: { address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5' }, + }, + { + label: 'DNSRegistry', + arg: 'luc.computer', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, + { + label: 'DNSRegistry', + arg: 'antony.sh', + expected: { address: '0x2B5c7025998f88550Ef2fEce8bf87935f542C190' }, + }, + { + label: 'CCIP Offchain RS', + arg: 'luc.willbreak.eth', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, + { + label: 'CCIP Coinbase', + arg: 'lucemans.cb.id', + expected: { address: '0x4e7abb71BEe38011c54c30D0130c0c71Da09222b' }, + }, ]; export const dataset_address_single: Dataset<{ name: string }> = [ - { label: "ETHRegistry", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.eth" } }, - { label: "ETHRegistry", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, + { + label: 'ETHRegistry', + arg: '0x225f137127d9067788314bc7fcc1f36746a3c3B5', + expected: { name: 'luc.eth' }, + }, + { + label: 'ETHRegistry', + arg: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5', + expected: { name: 'nick.eth' }, + }, // TODO: find another dns primary name address - // { label: "DNSRegistry", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.computer" } }, - { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, + // { + // label: 'DNSRegistry', + // arg: '0x225f137127d9067788314bc7fcc1f36746a3c3B5', + // expected: { name: 'luc.computer' }, + // }, + { + label: 'DNSRegistry', + arg: '0x2B5c7025998f88550Ef2fEce8bf87935f542C190', + expected: { name: 'antony.sh' }, + }, // TODO: find 2 ccip primary name addresses - // { label: "CCIP Offchain RS", arg: "0x225f137127d9067788314bc7fcc1f36746a3c3B5", expected: { name: "luc.willbreak.eth" } }, - // { label: "CCIP Coinbase", arg: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b", expected: { name: "lucemans.cb.id" } }, + // { + // label: 'CCIP Offchain RS', + // arg: '0x225f137127d9067788314bc7fcc1f36746a3c3B5', + // expected: { name: 'luc.willbreak.eth' }, + // }, + // { + // label: 'CCIP Coinbase', + // arg: '0x4e7abb71BEe38011c54c30D0130c0c71Da09222b', + // expected: { name: 'lucemans.cb.id' }, + // }, ]; -export const dataset_universal_single: Dataset<{ address: string} | {name: string }> = [ - { label: "ETHRegistry", arg: "luc.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "ETHRegistry", arg: "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5", expected: { name: "nick.eth" } }, - { label: "DNSRegistry", arg: "luc.computer", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, - { label: "DNSRegistry", arg: "0x2B5c7025998f88550Ef2fEce8bf87935f542C190", expected: { name: "antony.sh" } }, - { label: "CCIP Offchain RS", arg: "luc.willbreak.eth", expected: { address: "0x225f137127d9067788314bc7fcc1f36746a3c3B5" } }, +export const dataset_universal_single: Dataset<{ address: string } | { name: string }> = [ + { + label: 'ETHRegistry', + arg: 'luc.eth', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, + { + label: 'ETHRegistry', + arg: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5', + expected: { name: 'nick.eth' }, + }, + { + label: 'DNSRegistry', + arg: 'luc.computer', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, + { + label: 'DNSRegistry', + arg: '0x2B5c7025998f88550Ef2fEce8bf87935f542C190', + expected: { name: 'antony.sh' }, + }, + { + label: 'CCIP Offchain RS', + arg: 'luc.willbreak.eth', + expected: { address: '0x225f137127d9067788314bc7fcc1f36746a3c3B5' }, + }, // TODO: refer to above todo - // { label: "CCIP Coinbase", arg: "0x4e7abb71BEe38011c54c30D0130c0c71Da09222b", expected: { name: "lucemans.cb.id" } }, -] \ No newline at end of file + // { + // label: 'CCIP Coinbase', + // arg: '0x4e7abb71BEe38011c54c30D0130c0c71Da09222b', + // expected: { name: 'lucemans.cb.id' }, + // }, +]; diff --git a/test/package.json b/test/package.json index 3142999..dfc4013 100644 --- a/test/package.json +++ b/test/package.json @@ -1,15 +1,22 @@ { - "name": "enstate-tests", - "module": "index.ts", - "type": "module", - "devDependencies": { - "bun-types": "latest" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "dependencies": { - "@types/qs": "^6.9.11", - "qs": "^6.11.2" - } -} \ No newline at end of file + "name": "enstate-tests", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@typescript-eslint/parser": "^6.17.0", + "bun-types": "latest", + "eslint": "^8.56.0", + "eslint-plugin-v3xlabs": "^1.6.2", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@types/qs": "^6.9.11", + "qs": "^6.11.2" + }, + "scripts": { + "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./data" + } +} diff --git a/test/src/http_fetch.ts b/test/src/http_fetch.ts index 0d0dfb2..4fcaf73 100644 --- a/test/src/http_fetch.ts +++ b/test/src/http_fetch.ts @@ -1,4 +1,5 @@ export const http_fetch = (url: string) => async (input: string) => { - let result = await fetch(url + input); + const result = await fetch(url + input); + return await result.json(); }; diff --git a/test/src/test_implementation.ts b/test/src/test_implementation.ts index 7b7793b..fae29bf 100644 --- a/test/src/test_implementation.ts +++ b/test/src/test_implementation.ts @@ -1,11 +1,16 @@ -import { describe, expect, test } from "bun:test"; -import { Dataset } from "../data"; +import { describe, expect, test } from 'bun:test'; -export const test_implementation = , DataType extends {}>(function_name: string, fn: (input: string) => Promise>, dataset: DataSet) => { - describe("t/" + function_name, () => { +import { Dataset } from '../data'; + +export const test_implementation = , DataType extends {}>( + function_name: string, + resolve_function: (_input: string) => Promise>, + dataset: DataSet +) => { + describe('t/' + function_name, () => { for (const { label, arg, expected } of dataset) { test(label + ` (${arg})`, async () => { - let output = await fn(arg); + const output = await resolve_function(arg); expect(output).toMatchObject(expected); }); diff --git a/test/tests/server.spec.ts b/test/tests/server.spec.ts index 9142559..057a0a6 100644 --- a/test/tests/server.spec.ts +++ b/test/tests/server.spec.ts @@ -1,57 +1,82 @@ -import { expect, test, describe, beforeAll, afterAll } from "bun:test"; -import { test_implementation } from "../src/test_implementation"; -import { Subprocess } from "bun"; -import { http_fetch } from "../src/http_fetch"; -import { dataset_address_single, dataset_name_single, dataset_universal_single } from "../data/single"; -import { dataset_address_bulk, dataset_name_bulk, dataset_universal_bulk } from "../data"; +import { Subprocess } from 'bun'; +import { afterAll, beforeAll } from 'bun:test'; + +import { dataset_address_bulk, dataset_name_bulk, dataset_universal_bulk } from '../data'; +import { + dataset_address_single, + dataset_name_single, + dataset_universal_single, +} from '../data/single'; +import { http_fetch } from '../src/http_fetch'; +import { test_implementation } from '../src/test_implementation'; const TEST_RELEASE = true; -let server: Subprocess | undefined = undefined; +let server: Subprocess | undefined; beforeAll(async () => { - console.log("Building server..."); + console.log('Building server...'); await new Promise((resolve) => { - Bun.spawn(["cargo", "build", TEST_RELEASE ? "--release" : ''], { - cwd: "../server", onExit(proc, exitCode, signalCode, error) { + Bun.spawn(['cargo', 'build', TEST_RELEASE ? '--release' : ''], { + cwd: '../server', + onExit() { resolve(); - } + }, }); }); - console.log("Build finished!"); + console.log('Build finished!'); - server = Bun.spawn([`../server/target/${TEST_RELEASE ? 'release' : 'debug'}/enstate`], { cwd: "../server" }); + server = Bun.spawn([`../server/target/${TEST_RELEASE ? 'release' : 'debug'}/enstate`], { + cwd: '../server', + }); console.log('Waiting for server to start...'); let attempts = 0; + while (attempts < 10) { try { - console.log("Attempting heartbeat..."); - await fetch("http://0.0.0.0:3000/"); - console.log("Heartbeat succes!"); + console.log('Attempting heartbeat...'); + await fetch('http://0.0.0.0:3000/'); + console.log('Heartbeat succes!'); break; - } catch (e) { - console.log("Waiting another 1s for heartbeat..."); + } catch { + console.log('Waiting another 1s for heartbeat...'); attempts++; await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } } - console.log("Ready to start testing"); + console.log('Ready to start testing'); }); afterAll(async () => { server?.kill(); }); -test_implementation("server/name", http_fetch("http://0.0.0.0:3000/n/"), dataset_name_single); -test_implementation("server/address", http_fetch("http://0.0.0.0:3000/a/"), dataset_address_single); -test_implementation("server/universal", http_fetch("http://0.0.0.0:3000/u/"), dataset_universal_single); +test_implementation('server/name', http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); +test_implementation('server/address', http_fetch('http://0.0.0.0:3000/a/'), dataset_address_single); +test_implementation( + 'server/universal', + http_fetch('http://0.0.0.0:3000/u/'), + dataset_universal_single +); -test_implementation("server/bulk/name", http_fetch("http://0.0.0.0:3000/bulk/n?"), dataset_name_bulk); -test_implementation("server/bulk/address", http_fetch("http://0.0.0.0:3000/bulk/a?"), dataset_address_bulk); -test_implementation("server/bulk/universal", http_fetch("http://0.0.0.0:3000/bulk/u?"), dataset_universal_bulk); +test_implementation( + 'server/bulk/name', + http_fetch('http://0.0.0.0:3000/bulk/n?'), + dataset_name_bulk +); +test_implementation( + 'server/bulk/address', + http_fetch('http://0.0.0.0:3000/bulk/a?'), + dataset_address_bulk +); +test_implementation( + 'server/bulk/universal', + http_fetch('http://0.0.0.0:3000/bulk/u?'), + dataset_universal_bulk +); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index c05dab8..9036ff6 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -1,34 +1,41 @@ -import { expect, test, describe, beforeAll, afterAll } from "bun:test"; -import { test_implementation } from "../src/test_implementation"; -import { Subprocess } from "bun"; -import { dataset_address_single, dataset_name_single, dataset_universal_single } from "../data/single"; -import { http_fetch } from "../src/http_fetch"; +import { Subprocess } from 'bun'; +import { afterAll, beforeAll } from 'bun:test'; -let server: Subprocess | undefined = undefined; +import { dataset_address_bulk, dataset_name_bulk, dataset_universal_bulk } from '../data'; +import { + dataset_address_single, + dataset_name_single, + dataset_universal_single, +} from '../data/single'; +import { http_fetch } from '../src/http_fetch'; +import { test_implementation } from '../src/test_implementation'; + +let server: Subprocess | undefined; beforeAll(async () => { - console.log("Building worker..."); + console.log('Building worker...'); - server = Bun.spawn(['pnpm', 'dev', '--port', '3000'], { cwd: "../worker" }); + server = Bun.spawn(['pnpm', 'dev', '--port', '3000'], { cwd: '../worker' }); console.log('Waiting for server to start...'); let attempts = 0; + while (attempts < 30) { try { - console.log("Attempting heartbeat..."); - await fetch("http://0.0.0.0:3000/"); - console.log("Heartbeat succes!"); + console.log('Attempting heartbeat...'); + await fetch('http://0.0.0.0:3000/'); + console.log('Heartbeat succes!'); break; - } catch (e) { - console.log("Waiting another 1s for heartbeat..."); + } catch { + console.log('Waiting another 1s for heartbeat...'); attempts++; await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } } - console.log("Ready to start testing"); + console.log('Ready to start testing'); }); afterAll(async () => { @@ -37,6 +44,26 @@ afterAll(async () => { await server?.exited; }); -test_implementation("worker/name", http_fetch("http://0.0.0.0:3001/n/"), dataset_name_single); -test_implementation("worker/address", http_fetch("http://0.0.0.0:3001/n/"), dataset_address_single); -test_implementation("worker/universal", http_fetch("http://0.0.0.0:3001/n/"), dataset_universal_single); +test_implementation('worker/name', http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); +test_implementation('worker/address', http_fetch('http://0.0.0.0:3000/a/'), dataset_address_single); +test_implementation( + 'worker/universal', + http_fetch('http://0.0.0.0:3000/u/'), + dataset_universal_single +); + +test_implementation( + 'worker/bulk/name', + http_fetch('http://0.0.0.0:3000/bulk/n?'), + dataset_name_bulk +); +test_implementation( + 'worker/bulk/address', + http_fetch('http://0.0.0.0:3000/bulk/a?'), + dataset_address_bulk +); +test_implementation( + 'worker/bulk/universal', + http_fetch('http://0.0.0.0:3000/bulk/u?'), + dataset_universal_bulk +); From 85b40195150a124ff3789e26299b816248315b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sat, 6 Jan 2024 00:55:25 +0100 Subject: [PATCH 19/26] Fix worker bulk implementation & test worker action --- .github/workflows/test_worker.yml | 35 ++++++++++++++++++++++++++++++ test/bun.lockb | Bin 116945 -> 116945 bytes worker/src/bulk_util.rs | 35 ++++++++++++++++++++++++++++++ worker/src/http_util.rs | 19 ---------------- worker/src/lib.rs | 1 + worker/src/routes/address.rs | 6 ++--- worker/src/routes/name.rs | 6 ++--- worker/src/routes/universal.rs | 6 ++--- 8 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/test_worker.yml create mode 100644 worker/src/bulk_util.rs diff --git a/.github/workflows/test_worker.yml b/.github/workflows/test_worker.yml new file mode 100644 index 0000000..f2ce770 --- /dev/null +++ b/.github/workflows/test_worker.yml @@ -0,0 +1,35 @@ +on: + workflow_call: + +jobs: + test: + name: Test ENState Worker 🚀 + runs-on: ubuntu-latest + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - run: | + rustup set auto-self-update disable + rustup toolchain install stable --profile minimal + + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + with: + version: "v0.7.4" + + - uses: oven-sh/setup-bun@v1 + + - run: bun install + working-directory: test + + - name: Test + run: bun test tests/worker.spec.ts + working-directory: test + env: + RPC_URL: https://rpc.ankr.com/eth + OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }} diff --git a/test/bun.lockb b/test/bun.lockb index 5567ab3dc69b6bae4888ffc5ff664228ea5c7e96..99bce4f625f2f9de8e1ab4a7a552d85c3fd36aad 100755 GIT binary patch delta 26 icmcaOk^SOC_J%Etn@u1o+n*soYaR|`> delta 26 dcmcaOk^SOC_J%Etn@u>F7$9K#ZWBhaDFAey2TA|{ diff --git a/worker/src/bulk_util.rs b/worker/src/bulk_util.rs new file mode 100644 index 0000000..23ec9bd --- /dev/null +++ b/worker/src/bulk_util.rs @@ -0,0 +1,35 @@ +use crate::http_util::ValidationError; +use enstate_shared::utils::vec::dedup_ord; + +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct BulkResponse { + pub(crate) response_length: usize, + pub(crate) response: Vec, +} + +impl From> for BulkResponse { + fn from(value: Vec) -> Self { + BulkResponse { + response_length: value.len(), + response: value, + } + } +} + +pub fn validate_bulk_input( + input: &[String], + max_len: usize, +) -> Result, ValidationError> { + let unique = dedup_ord( + &input + .iter() + .map(|entry| entry.to_lowercase()) + .collect::>(), + ); + + if unique.len() > max_len { + return Err(ValidationError::MaxLengthExceeded(max_len)); + } + + Ok(unique) +} diff --git a/worker/src/http_util.rs b/worker/src/http_util.rs index 3a698a7..43437d0 100644 --- a/worker/src/http_util.rs +++ b/worker/src/http_util.rs @@ -1,5 +1,4 @@ use enstate_shared::models::profile::error::ProfileError; -use enstate_shared::utils::vec::dedup_ord; use ethers::prelude::ProviderError; use http::status::StatusCode; use serde::de::DeserializeOwned; @@ -47,24 +46,6 @@ impl From for worker::Error { } } -pub fn validate_bulk_input( - input: &[String], - max_len: usize, -) -> Result, ValidationError> { - let unique = dedup_ord( - &input - .iter() - .map(|entry| entry.to_lowercase()) - .collect::>(), - ); - - if unique.len() > max_len { - return Err(ValidationError::MaxLengthExceeded(max_len)); - } - - Ok(unique) -} - #[derive(Serialize)] pub struct ErrorResponse { pub(crate) status: u16, diff --git a/worker/src/lib.rs b/worker/src/lib.rs index 04845e0..b30188b 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -14,6 +14,7 @@ use worker::{event, Context, Cors, Env, Headers, Method, Request, Response, Rout use crate::http_util::http_simple_status_error; use crate::kv_cache::CloudflareKVCache; +mod bulk_util; mod http_util; mod kv_cache; mod routes; diff --git a/worker/src/routes/address.rs b/worker/src/routes/address.rs index 91b347d..35006f6 100644 --- a/worker/src/routes/address.rs +++ b/worker/src/routes/address.rs @@ -5,9 +5,9 @@ use http::StatusCode; use serde::Deserialize; use worker::{Request, Response, RouteContext}; +use crate::bulk_util::{validate_bulk_input, BulkResponse}; use crate::http_util::{ - http_simple_status_error, parse_query, profile_http_error_mapper, validate_bulk_input, - FreshQuery, + http_simple_status_error, parse_query, profile_http_error_mapper, FreshQuery, }; pub async fn get(req: Request, ctx: RouteContext) -> worker::Result { @@ -58,5 +58,5 @@ pub async fn get_bulk(req: Request, ctx: RouteContext) -> worker .await .map_err(profile_http_error_mapper)?; - Response::from_json(&joined) + Response::from_json(&BulkResponse::from(joined)) } diff --git a/worker/src/routes/name.rs b/worker/src/routes/name.rs index 0cde98f..c2540c2 100644 --- a/worker/src/routes/name.rs +++ b/worker/src/routes/name.rs @@ -4,9 +4,9 @@ use http::StatusCode; use serde::Deserialize; use worker::{Request, Response, RouteContext}; +use crate::bulk_util::{validate_bulk_input, BulkResponse}; use crate::http_util::{ - http_simple_status_error, parse_query, profile_http_error_mapper, validate_bulk_input, - FreshQuery, + http_simple_status_error, parse_query, profile_http_error_mapper, FreshQuery, }; pub async fn get(req: Request, ctx: RouteContext) -> worker::Result { @@ -47,5 +47,5 @@ pub async fn get_bulk(req: Request, ctx: RouteContext) -> worker .await .map_err(profile_http_error_mapper)?; - Response::from_json(&joined) + Response::from_json(&BulkResponse::from(joined)) } diff --git a/worker/src/routes/universal.rs b/worker/src/routes/universal.rs index 527c05d..6410fe7 100644 --- a/worker/src/routes/universal.rs +++ b/worker/src/routes/universal.rs @@ -4,9 +4,9 @@ use http::StatusCode; use serde::Deserialize; use worker::{Request, Response, RouteContext}; +use crate::bulk_util::{validate_bulk_input, BulkResponse}; use crate::http_util::{ - http_simple_status_error, parse_query, profile_http_error_mapper, validate_bulk_input, - FreshQuery, + http_simple_status_error, parse_query, profile_http_error_mapper, FreshQuery, }; pub async fn get(req: Request, ctx: RouteContext) -> worker::Result { @@ -50,5 +50,5 @@ pub async fn get_bulk(req: Request, ctx: RouteContext) -> worker .await .map_err(profile_http_error_mapper)?; - Response::from_json(&joined) + Response::from_json(&BulkResponse::from(joined)) } From 476dd193828bdd82500c5dbd5d78180abb8664c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:01:27 +0100 Subject: [PATCH 20/26] Update workflows --- .github/workflows/pr_check.yml | 2 +- .../workflows/{test_worker.yml => test.yml} | 4 +- .github/workflows/test_server.yml | 38 ------------------- 3 files changed, 3 insertions(+), 41 deletions(-) rename .github/workflows/{test_worker.yml => test.yml} (90%) delete mode 100644 .github/workflows/test_server.yml diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index db60fab..2887be7 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -40,5 +40,5 @@ jobs: - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} test_server: - uses: ./.github/workflows/test_server.yml + uses: ./.github/workflows/test.yml needs: [check] diff --git a/.github/workflows/test_worker.yml b/.github/workflows/test.yml similarity index 90% rename from .github/workflows/test_worker.yml rename to .github/workflows/test.yml index f2ce770..c459e0e 100644 --- a/.github/workflows/test_worker.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ on: jobs: test: - name: Test ENState Worker 🚀 + name: Test ENState 🚀 runs-on: ubuntu-latest env: SCCACHE_GHA_ENABLED: "true" @@ -28,7 +28,7 @@ jobs: working-directory: test - name: Test - run: bun test tests/worker.spec.ts + run: bun test working-directory: test env: RPC_URL: https://rpc.ankr.com/eth diff --git a/.github/workflows/test_server.yml b/.github/workflows/test_server.yml deleted file mode 100644 index d91b9f9..0000000 --- a/.github/workflows/test_server.yml +++ /dev/null @@ -1,38 +0,0 @@ -on: - workflow_call: - -jobs: - test: - name: Test ENState 🚀 - runs-on: ubuntu-latest - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - run: | - rustup set auto-self-update disable - rustup toolchain install stable --profile minimal - - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 - with: - version: "v0.7.4" - - - run: cargo build --release - working-directory: server - - - uses: oven-sh/setup-bun@v1 - - - run: bun install - working-directory: test - - - name: Test - run: bun test tests/server.spec.ts - working-directory: test - env: - RPC_URL: https://rpc.ankr.com/eth - OPENSEA_API_KEY: ${{ secrets.OPENSEA_API_KEY }} From 3ba3be5db8fc29412c40945e7ba4a4232c7d834a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:03:38 +0100 Subject: [PATCH 21/26] Update workflows --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 480d72f..f6ac901 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ env: jobs: test_server: - uses: ./.github/workflows/test_server.yml + uses: ./.github/workflows/test.yml build: name: Build ENState 🚀 runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c459e0e..ec23201 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,7 @@ on: + push: + branches: + - ci/integration-tests workflow_call: jobs: From a1fbf80ee055fb80925d331e6bd0fde9bc0eaff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:16:02 +0100 Subject: [PATCH 22/26] Update workflow --- .github/workflows/pr_check.yml | 2 +- .github/workflows/test.yml | 6 ++++++ test/tests/worker.spec.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 2887be7..a39c070 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -39,6 +39,6 @@ jobs: - run: cargo check --target ${{ matrix.target }} --release working-directory: ${{ matrix.path }} - test_server: + test: uses: ./.github/workflows/test.yml needs: [check] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec23201..ae53e08 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,12 @@ jobs: - run: bun install working-directory: test + # a great deal of effort was put here not to have to install pnpm lol + - run: bunx pnpm install + working-directory: worker + + - run: bun install --global wrangler + - name: Test run: bun test working-directory: test diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 9036ff6..129f6b4 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -15,7 +15,7 @@ let server: Subprocess | undefined; beforeAll(async () => { console.log('Building worker...'); - server = Bun.spawn(['pnpm', 'dev', '--port', '3000'], { cwd: '../worker' }); + server = Bun.spawn(['wrangler', 'dev', '--port', '3000'], { cwd: '../worker' }); console.log('Waiting for server to start...'); From 250f452344ee8ad5ed605437d37a1c6bfffc0d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:27:20 +0100 Subject: [PATCH 23/26] Update workflow --- .github/workflows/test.yml | 4 ---- test/tests/worker.spec.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae53e08..2fdef9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,10 +30,6 @@ jobs: - run: bun install working-directory: test - # a great deal of effort was put here not to have to install pnpm lol - - run: bunx pnpm install - working-directory: worker - - run: bun install --global wrangler - name: Test diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 129f6b4..4614b51 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -28,9 +28,9 @@ beforeAll(async () => { console.log('Heartbeat succes!'); break; } catch { - console.log('Waiting another 1s for heartbeat...'); + console.log('Waiting another 4s for heartbeat...'); attempts++; - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 4000)); continue; } } From 749938853981708f6e9ef0f54ff60e762bd42c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:49:06 +0100 Subject: [PATCH 24/26] Update test --- test/tests/worker.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 4614b51..12ba215 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -10,7 +10,7 @@ import { import { http_fetch } from '../src/http_fetch'; import { test_implementation } from '../src/test_implementation'; -let server: Subprocess | undefined; +let server: Subprocess<'ignore', 'pipe', 'inherit'> | undefined; beforeAll(async () => { console.log('Building worker...'); @@ -21,7 +21,9 @@ beforeAll(async () => { let attempts = 0; - while (attempts < 30) { + // TODO: fix + // eslint-disable-next-line no-constant-condition + while (true) { try { console.log('Attempting heartbeat...'); await fetch('http://0.0.0.0:3000/'); @@ -30,7 +32,7 @@ beforeAll(async () => { } catch { console.log('Waiting another 4s for heartbeat...'); attempts++; - await new Promise((resolve) => setTimeout(resolve, 4000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } } From 877d2148610cbf4ca6fdb75e5c8cc26e06428b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 18:57:57 +0100 Subject: [PATCH 25/26] Update workflow --- .github/workflows/test.yml | 6 +++++- test/tests/server.spec.ts | 18 ++++++++++++------ test/tests/worker.spec.ts | 23 +++++++++++++---------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2fdef9e..a8e782d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,9 @@ jobs: env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" + strategy: + matrix: + suite: [server, worker] steps: - uses: actions/checkout@v3 with: @@ -31,9 +34,10 @@ jobs: working-directory: test - run: bun install --global wrangler + if: ${{ matrix.suite == 'worker' }} - name: Test - run: bun test + run: bun test ${{ matrix.suite }} working-directory: test env: RPC_URL: https://rpc.ankr.com/eth diff --git a/test/tests/server.spec.ts b/test/tests/server.spec.ts index 057a0a6..f8363d6 100644 --- a/test/tests/server.spec.ts +++ b/test/tests/server.spec.ts @@ -57,26 +57,32 @@ afterAll(async () => { server?.kill(); }); -test_implementation('server/name', http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); -test_implementation('server/address', http_fetch('http://0.0.0.0:3000/a/'), dataset_address_single); +const PREFIX = 'server'; + +test_implementation(`${PREFIX}/name`, http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); +test_implementation( + `${PREFIX}/address`, + http_fetch('http://0.0.0.0:3000/a/'), + dataset_address_single +); test_implementation( - 'server/universal', + `${PREFIX}/universal`, http_fetch('http://0.0.0.0:3000/u/'), dataset_universal_single ); test_implementation( - 'server/bulk/name', + `${PREFIX}/bulk/name`, http_fetch('http://0.0.0.0:3000/bulk/n?'), dataset_name_bulk ); test_implementation( - 'server/bulk/address', + `${PREFIX}/bulk/address`, http_fetch('http://0.0.0.0:3000/bulk/a?'), dataset_address_bulk ); test_implementation( - 'server/bulk/universal', + `${PREFIX}/bulk/universal`, http_fetch('http://0.0.0.0:3000/bulk/u?'), dataset_universal_bulk ); diff --git a/test/tests/worker.spec.ts b/test/tests/worker.spec.ts index 12ba215..6b64ee6 100644 --- a/test/tests/worker.spec.ts +++ b/test/tests/worker.spec.ts @@ -19,8 +19,6 @@ beforeAll(async () => { console.log('Waiting for server to start...'); - let attempts = 0; - // TODO: fix // eslint-disable-next-line no-constant-condition while (true) { @@ -30,8 +28,7 @@ beforeAll(async () => { console.log('Heartbeat succes!'); break; } catch { - console.log('Waiting another 4s for heartbeat...'); - attempts++; + console.log('Waiting another 1s for heartbeat...'); await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } @@ -46,26 +43,32 @@ afterAll(async () => { await server?.exited; }); -test_implementation('worker/name', http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); -test_implementation('worker/address', http_fetch('http://0.0.0.0:3000/a/'), dataset_address_single); +const PREFIX = 'worker'; + +test_implementation(`${PREFIX}/name`, http_fetch('http://0.0.0.0:3000/n/'), dataset_name_single); +test_implementation( + `${PREFIX}/address`, + http_fetch('http://0.0.0.0:3000/a/'), + dataset_address_single +); test_implementation( - 'worker/universal', + `${PREFIX}/universal`, http_fetch('http://0.0.0.0:3000/u/'), dataset_universal_single ); test_implementation( - 'worker/bulk/name', + `${PREFIX}/bulk/name`, http_fetch('http://0.0.0.0:3000/bulk/n?'), dataset_name_bulk ); test_implementation( - 'worker/bulk/address', + `${PREFIX}/bulk/address`, http_fetch('http://0.0.0.0:3000/bulk/a?'), dataset_address_bulk ); test_implementation( - 'worker/bulk/universal', + `${PREFIX}/bulk/universal`, http_fetch('http://0.0.0.0:3000/bulk/u?'), dataset_universal_bulk ); From 53f47ab53345fc3eace337f077e85ad94e6953c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20F=2E=20=C5=A0tignjedec?= Date: Sun, 7 Jan 2024 19:00:44 +0100 Subject: [PATCH 26/26] Update workflow --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8e782d..ad13403 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,4 @@ on: - push: - branches: - - ci/integration-tests workflow_call: jobs: