Crate strategy test
This commit is contained in:
parent
1ef763fcfb
commit
050e117a16
|
@ -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",
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface DynamicStrategy {
|
||||
createInstance(): any;
|
||||
getName(): string;
|
||||
apply(chain: string): boolean;
|
||||
}
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export interface RepositoryStrategy {
|
||||
apply(): boolean;
|
||||
getName(): string;
|
||||
export interface StaticStrategy {
|
||||
createInstance(): any;
|
||||
getName(): string;
|
||||
apply(): boolean;
|
||||
}
|
|
@ -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) => {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue