Crate strategy test

This commit is contained in:
Julian Merlo 2023-11-28 15:47:46 -03:00
parent 1ef763fcfb
commit 050e117a16
15 changed files with 126 additions and 76 deletions

View File

@ -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",

View File

@ -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<bigint> {

View File

@ -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<any>, RepositoryStrategy {
export class FileMetadataRepository implements MetadataRepository<any>, 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<any>, Reposito
}
createInstance() {
return new FileMetadataRepository(this.cfg, this.dirPath);
return new FileMetadataRepository(this.cfg);
}
async get(id: string): Promise<any> {

View File

@ -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<string, prometheus.Counter<string>> = new Map();
private gauges: Map<string, prometheus.Gauge<string>> = new Map();
private readonly registry: prometheus.Registry;

View File

@ -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;

View File

@ -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;

View File

@ -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<number> {

View File

@ -0,0 +1,5 @@
export interface DynamicStrategy {
createInstance(): any;
getName(): string;
apply(chain: string): boolean;
}

View File

@ -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<string, any> {
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());
});
});

View File

@ -1,5 +1,5 @@
export interface RepositoryStrategy {
apply(): boolean;
getName(): string;
export interface StaticStrategy {
createInstance(): any;
getName(): string;
apply(): boolean;
}

View File

@ -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) => {

View File

@ -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)) {

View File

@ -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);
});
});

View File

@ -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",
},

View File

@ -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<string, PlatformConfig> = {
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;