From 050e117a16b879e86bde7f9f554d5ff0ae545683 Mon Sep 17 00:00:00 2001 From: Julian Merlo Date: Tue, 28 Nov 2023 15:47:46 -0300 Subject: [PATCH] Crate strategy test --- .../src/infrastructure/RepositoriesBuilder.ts | 9 ++-- .../repositories/EvmJsonRPCBlockRepository.ts | 27 +++++------ .../repositories/FileMetadataRepository.ts | 10 ++-- .../repositories/PromStatRepository.ts | 4 +- .../repositories/SnsEventRepository.ts | 4 +- .../repositories/StaticJobRepository.ts | 2 +- .../repositories/Web3SolanaSlotRepository.ts | 21 ++++----- .../strategies/DynamicStrategy.ts | 5 ++ .../strategies/RepositoriesStrategy.ts | 20 ++++---- ...epositoryStrategy.ts => StaticStrategy.ts} | 6 +-- ...JsonRPCBlockRepository.integration.test.ts | 5 +- .../repositories/FileMetadataRepo.test.ts | 2 +- .../repositories/RepositoriesBuilder.test.ts | 46 +++++++++++++++++++ .../Web3SolanaSlotRepository.test.ts | 29 ++++++------ blockchain-watcher/test/mock/configMock.ts | 12 ++--- 15 files changed, 126 insertions(+), 76 deletions(-) create mode 100644 blockchain-watcher/src/infrastructure/repositories/strategies/DynamicStrategy.ts rename blockchain-watcher/src/infrastructure/repositories/strategies/{RepositoryStrategy.ts => StaticStrategy.ts} (64%) create mode 100644 blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts diff --git a/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts b/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts index d1ad39bf..ddf8e0d7 100644 --- a/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts +++ b/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts @@ -29,9 +29,12 @@ export class RepositoriesBuilder { const staticRepositories = repositoryStrategy.executeStatic(); const dynamicRepositories = repositoryStrategy.executeDynamic(); - this.repositories = staticRepositories; - this.repositories = {...this.repositories, dynamicRepositories} - dynamicRepositories.forEach((instance, name) => { this.repositories.set(name, instance) }); + staticRepositories.forEach((instance, name) => { + this.repositories.set(name, instance); + }); + dynamicRepositories.forEach((instance, name) => { + this.repositories.set(name, instance); + }); this.repositories.set( "jobs", diff --git a/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts index 1ea8cb40..41b9827f 100644 --- a/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts @@ -4,50 +4,51 @@ import winston from "../log"; import { HttpClient } from "../http/HttpClient"; import { HttpClientError } from "../../domain/errors"; import { Config } from "../config"; -import { RepositoryStrategy } from "./strategies/RepositoryStrategy"; +import { DynamicStrategy } from "./strategies/DynamicStrategy"; /** * EvmJsonRPCBlockRepository is a repository that uses a JSON RPC endpoint to fetch blocks. * On the reliability side, only knows how to timeout. */ -export class EvmJsonRPCBlockRepository implements EvmBlockRepository, RepositoryStrategy { + +const CHAIN = "ethereum"; + +export class EvmJsonRPCBlockRepository implements EvmBlockRepository, DynamicStrategy { private readonly logger = winston.child({ module: "EvmJsonRPCBlockRepository" }); private httpClient: HttpClient; - private chain: string; private cfg: Config; private rpc: URL; - constructor(cfg: Config, chain: string) { + constructor(cfg: Config) { const repoCfg: EvmJsonRPCBlockRepositoryCfg = { - chain, - rpc: cfg.platforms[chain].rpcs[0], - timeout: cfg.platforms[chain].timeout, + chain: CHAIN, + rpc: cfg.platforms[CHAIN].rpcs[0], + timeout: cfg.platforms[CHAIN].timeout, }; this.logger = winston.child({ module: "EvmJsonRPCBlockRepository", chain: repoCfg.chain }); this.httpClient = new HttpClient({ retries: 3, - timeout: cfg.platforms[chain].timeout ?? 5_000, + timeout: cfg.platforms[CHAIN].timeout ?? 5_000, initialDelay: 1_000, maxDelay: 30_000, }); this.rpc = new URL(repoCfg.rpc); - this.chain = chain; this.cfg = cfg; } - apply(): boolean { - return this.chain === "ethereum"; + apply(chain: string): boolean { + return chain === CHAIN; } getName(): string { - return `${this.chain}-evmRepo`; + return `${CHAIN}-evmRepo`; } createInstance(): EvmJsonRPCBlockRepository { - return new EvmJsonRPCBlockRepository(this.cfg, this.chain); + return new EvmJsonRPCBlockRepository(this.cfg); } async getBlockHeight(finality: EvmTag): Promise { diff --git a/blockchain-watcher/src/infrastructure/repositories/FileMetadataRepository.ts b/blockchain-watcher/src/infrastructure/repositories/FileMetadataRepository.ts index 5ca5aaa6..6931c4e9 100644 --- a/blockchain-watcher/src/infrastructure/repositories/FileMetadataRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/FileMetadataRepository.ts @@ -1,17 +1,17 @@ import { MetadataRepository } from "../../domain/repositories"; import fs from "fs"; -import { RepositoryStrategy } from "./strategies/RepositoryStrategy"; +import { StaticStrategy } from "./strategies/StaticStrategy"; import { Config } from "../config"; const UTF8 = "utf8"; -export class FileMetadataRepository implements MetadataRepository, RepositoryStrategy { +export class FileMetadataRepository implements MetadataRepository, StaticStrategy { private readonly dirPath: string; private readonly cfg: Config; - constructor(cfg: Config, dirPath: string) { + constructor(cfg: Config) { this.cfg = cfg; - this.dirPath = dirPath; + this.dirPath = this.cfg.metadata?.dir!; if (!fs.existsSync(this.dirPath)) { fs.mkdirSync(this.dirPath, { recursive: true }); @@ -27,7 +27,7 @@ export class FileMetadataRepository implements MetadataRepository, Reposito } createInstance() { - return new FileMetadataRepository(this.cfg, this.dirPath); + return new FileMetadataRepository(this.cfg); } async get(id: string): Promise { diff --git a/blockchain-watcher/src/infrastructure/repositories/PromStatRepository.ts b/blockchain-watcher/src/infrastructure/repositories/PromStatRepository.ts index 48f4412e..3b0668eb 100644 --- a/blockchain-watcher/src/infrastructure/repositories/PromStatRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/PromStatRepository.ts @@ -1,8 +1,8 @@ import prometheus from "prom-client"; import { StatRepository } from "../../domain/repositories"; -import { RepositoryStrategy } from "./strategies/RepositoryStrategy"; +import { StaticStrategy } from "./strategies/StaticStrategy"; -export class PromStatRepository implements StatRepository, RepositoryStrategy { +export class PromStatRepository implements StatRepository, StaticStrategy { private counters: Map> = new Map(); private gauges: Map> = new Map(); private readonly registry: prometheus.Registry; diff --git a/blockchain-watcher/src/infrastructure/repositories/SnsEventRepository.ts b/blockchain-watcher/src/infrastructure/repositories/SnsEventRepository.ts index 63c6c44a..2471b66d 100644 --- a/blockchain-watcher/src/infrastructure/repositories/SnsEventRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/SnsEventRepository.ts @@ -8,7 +8,7 @@ import { } from "@aws-sdk/client-sns"; import winston from "../log"; import { SnsEvent } from "../events/SnsEvent"; -import { RepositoryStrategy } from "./strategies/RepositoryStrategy"; +import { StaticStrategy } from "./strategies/StaticStrategy"; import { Config } from "../config"; import { SnsRepository } from "../../domain/repositories"; @@ -19,7 +19,7 @@ const SUCCESS_STATUS = "success"; const ERROR_STATUS = "error"; const CHUNK_SIZE = 10; -export class SnsEventRepository implements SnsRepository, RepositoryStrategy { +export class SnsEventRepository implements SnsRepository, StaticStrategy { private logger: winston.Logger; private client: SNSClient; private snsCfg: SnsConfig; diff --git a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts index 9142a36d..d7be07ea 100644 --- a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts @@ -45,7 +45,7 @@ export class StaticJobRepository implements JobRepository { solanaSlotRepo: SolanaSlotRepository; } ) { - this.fileRepo = new FileMetadataRepository(cfg, cfg.jobs.dir); + this.fileRepo = new FileMetadataRepository(cfg); this.blockRepoProvider = blockRepoProvider; this.metadataRepo = repos.metadataRepo; this.statsRepo = repos.statsRepo; diff --git a/blockchain-watcher/src/infrastructure/repositories/Web3SolanaSlotRepository.ts b/blockchain-watcher/src/infrastructure/repositories/Web3SolanaSlotRepository.ts index 7df741a4..07c8bebe 100644 --- a/blockchain-watcher/src/infrastructure/repositories/Web3SolanaSlotRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/Web3SolanaSlotRepository.ts @@ -8,35 +8,34 @@ import { import { solana } from "../../domain/entities"; import { SolanaSlotRepository } from "../../domain/repositories"; import { Fallible, SolanaFailure } from "../../domain/errors"; -import { RepositoryStrategy } from "./strategies/RepositoryStrategy"; +import { DynamicStrategy } from "./strategies/DynamicStrategy"; import { Config } from "../config"; const COMMITMENT_FINALIZED = "finalized"; const COMMITMENT_CONDIRMED = "confirmed"; const LEGACY_VERSION = "legacy"; +const CHAIN = "solana"; +const NAME = "solana-slotRepo"; -export class Web3SolanaSlotRepository implements SolanaSlotRepository, RepositoryStrategy { +export class Web3SolanaSlotRepository implements SolanaSlotRepository, DynamicStrategy { private connection: Connection; - private chain: string; private cfg: Config; - constructor(connection: Connection, cfg: Config, chain: string) { - this.connection = connection; + constructor(cfg: Config) { + this.connection = new Connection(cfg.platforms[CHAIN].rpcs[0]); this.cfg = cfg; - this.chain = chain; } - apply(): boolean { - return this.chain === "solana"; + apply(chain: string): boolean { + return chain === CHAIN; } getName(): string { - return "solana-slotRepo"; + return NAME; } createInstance() { - const config = this.cfg.platforms[this.chain]; - return new Web3SolanaSlotRepository(new Connection(config.rpcs[0]), this.cfg, this.chain); + return new Web3SolanaSlotRepository(this.cfg); } getLatestSlot(commitment: string): Promise { diff --git a/blockchain-watcher/src/infrastructure/repositories/strategies/DynamicStrategy.ts b/blockchain-watcher/src/infrastructure/repositories/strategies/DynamicStrategy.ts new file mode 100644 index 00000000..cbe9cc81 --- /dev/null +++ b/blockchain-watcher/src/infrastructure/repositories/strategies/DynamicStrategy.ts @@ -0,0 +1,5 @@ +export interface DynamicStrategy { + createInstance(): any; + getName(): string; + apply(chain: string): boolean; +} diff --git a/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoriesStrategy.ts b/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoriesStrategy.ts index afd89411..69e68598 100644 --- a/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoriesStrategy.ts +++ b/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoriesStrategy.ts @@ -6,6 +6,8 @@ import { Config } from "../../config"; import { EvmJsonRPCBlockRepository } from "../EvmJsonRPCBlockRepository"; import { Web3SolanaSlotRepository } from "../Web3SolanaSlotRepository"; import { Connection } from "@solana/web3.js"; +import { DynamicStrategy } from "./DynamicStrategy"; +import { StaticStrategy } from "./StaticStrategy"; export class RepositoriesStrategy { private snsClient?: SNSClient; @@ -19,9 +21,9 @@ export class RepositoriesStrategy { executeStatic(): Map { let staticRepositories = new Map(); - const repositories = [ + const repositories: StaticStrategy[] = [ new SnsEventRepository(this.snsClient!, this.cfg), - new FileMetadataRepository(this.cfg, this.cfg.metadata?.dir!), + new FileMetadataRepository(this.cfg), new PromStatRepository(), ]; @@ -37,17 +39,15 @@ export class RepositoriesStrategy { let dynamicRepositories = new Map(); this.cfg.supportedChains.forEach((chain) => { - const config = this.cfg.platforms[chain]; - - const repositories = [ - new EvmJsonRPCBlockRepository(this.cfg, chain), - new Web3SolanaSlotRepository(new Connection(config.rpcs[0]), this.cfg, chain), - ]; - if (!this.cfg.platforms[chain]) throw new Error(`No config for chain ${chain}`); + const repositories: DynamicStrategy[] = [ + new EvmJsonRPCBlockRepository(this.cfg), + new Web3SolanaSlotRepository(this.cfg), + ]; + repositories.forEach((repository) => { - if (repository.apply()) + if (repository.apply(chain)) dynamicRepositories.set(repository.getName(), repository.createInstance()); }); }); diff --git a/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoryStrategy.ts b/blockchain-watcher/src/infrastructure/repositories/strategies/StaticStrategy.ts similarity index 64% rename from blockchain-watcher/src/infrastructure/repositories/strategies/RepositoryStrategy.ts rename to blockchain-watcher/src/infrastructure/repositories/strategies/StaticStrategy.ts index 3193936e..3a52352a 100644 --- a/blockchain-watcher/src/infrastructure/repositories/strategies/RepositoryStrategy.ts +++ b/blockchain-watcher/src/infrastructure/repositories/strategies/StaticStrategy.ts @@ -1,5 +1,5 @@ -export interface RepositoryStrategy { - apply(): boolean; - getName(): string; +export interface StaticStrategy { createInstance(): any; + getName(): string; + apply(): boolean; } diff --git a/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts b/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts index d2010706..2500b1cd 100644 --- a/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts @@ -29,7 +29,7 @@ describe("EvmJsonRPCBlockRepository", () => { givenBlockHeightIs(expectedHeight, "latest"); // When - const result = await repo.apply(); + const result = await repo.apply("ethereum"); // Then expect(result).toBe(true); @@ -113,9 +113,8 @@ describe("EvmJsonRPCBlockRepository", () => { }); const givenARepo = () => { - const chain = "ethereum"; const cfg = configMock(); - repo = new EvmJsonRPCBlockRepository(cfg, chain); + repo = new EvmJsonRPCBlockRepository(cfg); }; const givenBlockHeightIs = (height: bigint, commitment: EvmTag) => { diff --git a/blockchain-watcher/test/infrastructure/repositories/FileMetadataRepo.test.ts b/blockchain-watcher/test/infrastructure/repositories/FileMetadataRepo.test.ts index 2e8f7968..f6c440a3 100644 --- a/blockchain-watcher/test/infrastructure/repositories/FileMetadataRepo.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/FileMetadataRepo.test.ts @@ -6,7 +6,7 @@ import { configMock } from "../../mock/configMock"; describe("FileMetadataRepository", () => { const dirPath = "./metadata-repo"; const cfg = configMock(); - const repo = new FileMetadataRepository(cfg, dirPath); + const repo = new FileMetadataRepository(cfg); beforeEach(() => { if (!fs.existsSync(dirPath)) { diff --git a/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts new file mode 100644 index 00000000..1cf36c69 --- /dev/null +++ b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "@jest/globals"; +import { RepositoriesBuilder } from "../../../src/infrastructure/RepositoriesBuilder"; +import { configMock } from "../../mock/configMock"; +import { + EvmJsonRPCBlockRepository, + FileMetadataRepository, + PromStatRepository, + SnsEventRepository, + Web3SolanaSlotRepository, +} from "../../../src/infrastructure/repositories"; + +describe("RepositoriesBuilder", () => { + it("should be error because dose not have any chain", async () => { + try { + // When + new RepositoriesBuilder(configMock([])); + } catch (e) { + // Then + expect(e).toBeInstanceOf(Error); + } + }); + + it("should be error because dose not support test chain", async () => { + try { + // When + new RepositoriesBuilder(configMock(["test"])); + } catch (e) { + // Then + expect(e).toBeInstanceOf(Error); + } + }); + + it("should be return all repositories instances", async () => { + // When + const repos = new RepositoriesBuilder(configMock(["ethereum", "solana"])); + // Then + const job = repos.getJobsRepository(); + expect(job).toBeTruthy() + + expect(repos.getEvmBlockRepository("ethereum")).toBeInstanceOf(EvmJsonRPCBlockRepository); + expect(repos.getMetadataRepository()).toBeInstanceOf(FileMetadataRepository); + expect(repos.getSnsEventRepository()).toBeInstanceOf(SnsEventRepository); + expect(repos.getStatsRepository()).toBeInstanceOf(PromStatRepository); + expect(repos.getSolanaSlotRepository()).toBeInstanceOf(Web3SolanaSlotRepository); + }); +}); diff --git a/blockchain-watcher/test/infrastructure/repositories/Web3SolanaSlotRepository.test.ts b/blockchain-watcher/test/infrastructure/repositories/Web3SolanaSlotRepository.test.ts index 09b6d630..fbf23d55 100644 --- a/blockchain-watcher/test/infrastructure/repositories/Web3SolanaSlotRepository.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/Web3SolanaSlotRepository.test.ts @@ -4,17 +4,16 @@ import { Web3SolanaSlotRepository } from "../../../src/infrastructure/repositori import { configMock } from "../../mock/configMock"; describe("Web3SolanaSlotRepository", () => { - const chain = "ethereum"; + const chain = "solana"; const cfg = configMock(); describe("strategy", () => { it("should be apply Web3SolanaSlotRepository", async () => { // Given - const connectionMock = {}; - const repo = new Web3SolanaSlotRepository(connectionMock as any, cfg, "solana"); + const repo = new Web3SolanaSlotRepository(cfg); // When - const result = await repo.apply(); + const result = await repo.apply(chain); // Then expect(result).toBe(true); @@ -22,8 +21,7 @@ describe("Web3SolanaSlotRepository", () => { it("should be get name metadata", async () => { // Given - const connectionMock = {}; - const repo = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When const result = await repo.getName(); @@ -34,8 +32,7 @@ describe("Web3SolanaSlotRepository", () => { it("should be create instance", async () => { // Given - const connectionMock = {}; - const repo = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When const result = await repo.createInstance(); @@ -51,10 +48,10 @@ describe("Web3SolanaSlotRepository", () => { const connectionMock = { getSlot: () => Promise.resolve(100), }; - const repository = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When - const latestSlot = await repository.getLatestSlot("finalized"); + const latestSlot = await repo.getLatestSlot("finalized"); // Then expect(latestSlot).toBe(100); @@ -71,10 +68,10 @@ describe("Web3SolanaSlotRepository", () => { const connectionMock = { getBlock: (slot: number) => Promise.resolve(expected), }; - const repository = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When - const block = (await repository.getBlock(100)).getValue(); + const block = (await repo.getBlock(100)).getValue(); // Then expect(block.blockTime).toBe(expected.blockTime); @@ -98,10 +95,10 @@ describe("Web3SolanaSlotRepository", () => { const connectionMock = { getSignaturesForAddress: () => Promise.resolve(expected), }; - const repository = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When - const signatures = await repository.getSignaturesForAddress( + const signatures = await repo.getSignaturesForAddress( "BTcueXFisZiqE49Ne2xTZjHV9bT5paVZhpKc1k4L3n1c", "before", "after", @@ -133,10 +130,10 @@ describe("Web3SolanaSlotRepository", () => { const connectionMock = { getTransactions: (sigs: solana.ConfirmedSignatureInfo[]) => Promise.resolve(expected), }; - const repository = new Web3SolanaSlotRepository(connectionMock as any, cfg, chain); + const repo = new Web3SolanaSlotRepository(cfg); // When - const transactions = await repository.getTransactions([ + const transactions = await repo.getTransactions([ { signature: "signature1", }, diff --git a/blockchain-watcher/test/mock/configMock.ts b/blockchain-watcher/test/mock/configMock.ts index 231c8636..a5a56eda 100644 --- a/blockchain-watcher/test/mock/configMock.ts +++ b/blockchain-watcher/test/mock/configMock.ts @@ -1,17 +1,17 @@ import { SnsConfig } from "../../src/infrastructure/repositories"; import { Config, PlatformConfig } from "../../src/infrastructure/config"; -export const configMock = (): Config => { +export const configMock = (chains: string[] = []): Config => { const platformRecord: Record = { - latest: { - name: "test", + ethereum: { + name: "ethereum", network: "ETH", chainId: 1222341, rpcs: ["http://localhost"], timeout: 100, }, - ethereum: { - name: "test", + solana: { + name: "solana", network: "ETH", chainId: 1222341, rpcs: ["http://localhost"], @@ -44,7 +44,7 @@ export const configMock = (): Config => { dir: "./metadata-repo/jobs", }, platforms: platformRecord, - supportedChains: [], + supportedChains: chains, }; return cfg;