diff --git a/rollup.config.mjs b/rollup.config.mjs index a26125a86..57dd47c5f 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -43,15 +43,6 @@ export default [ json(), // Required because one our dependencies (bottleneck) loads its own 'version.json' typescript({ tsconfig: "./tsconfig.json" }), terser({ keep_classnames: true }), - { - // Temporary workaround https://github.com/rollup/rollup/issues/4213 - closeBundle() { - if (!process.env.ROLLUP_WATCH) { - setTimeout(() => process.exit(0)); - } - }, - name: "force-close", - }, ], }, // NodeJS diff --git a/src/activity/activity.ts b/src/activity/activity.ts index d03cbaf48..80e8e723a 100644 --- a/src/activity/activity.ts +++ b/src/activity/activity.ts @@ -6,6 +6,7 @@ import sleep from "../utils/sleep"; import { ActivityFactory } from "./factory"; import { ActivityConfig } from "./config"; import { Events } from "../events"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * @hidden @@ -27,12 +28,6 @@ export interface ExeScriptRequest { } export interface ActivityOptions { - yagnaOptions?: { - /** Yagna Api Key */ - apiKey?: string; - /** Yagna base path to Activity REST Api */ - basePath?: string; - }; /** timeout for sending and creating batch */ activityRequestTimeout?: number; /** timeout for executing batch */ @@ -58,12 +53,14 @@ export class Activity { /** * @param id activity ID * @param agreementId agreement ID + * @param yagnaApi - {@link YagnaApi} * @param options - {@link ActivityOptions} * @hidden */ constructor( public readonly id, public readonly agreementId, + protected readonly yagnaApi: YagnaApi, protected readonly options: ActivityConfig, ) { this.logger = options?.logger; @@ -73,12 +70,18 @@ export class Activity { * Create activity for given agreement ID * * @param agreementId + * @param yagnaApi * @param options - {@link ActivityOptions} * @param secure - defines if activity will be secure type * @return Activity */ - static async create(agreementId: string, options?: ActivityOptions, secure = false): Promise { - const factory = new ActivityFactory(agreementId, options); + static async create( + agreementId: string, + yagnaApi: YagnaApi, + options?: ActivityOptions, + secure = false, + ): Promise { + const factory = new ActivityFactory(agreementId, yagnaApi, options); return factory.create(secure); } @@ -128,7 +131,7 @@ export class Activity { */ public async getState(): Promise { try { - const { data } = await this.options.api.state.getActivityState(this.id); + const { data } = await this.yagnaApi.activity.state.getActivityState(this.id); const state = data.state[0]; if (this.currentState !== ActivityStateEnum[state]) { this.options.eventTarget?.dispatchEvent( @@ -144,14 +147,14 @@ export class Activity { } protected async send(script: ExeScriptRequest): Promise { - const { data: batchId } = await this.options.api.control.exec(this.id, script, { + const { data: batchId } = await this.yagnaApi.activity.control.exec(this.id, script, { timeout: this.options.activityRequestTimeout, }); return batchId; } private async end() { - await this.options.api.control + await this.yagnaApi.activity.control .destroyActivity(this.id, this.options.activityRequestTimeout / 1000, { timeout: this.options.activityRequestTimeout + 1000, }) @@ -161,7 +164,6 @@ export class Activity { ); }); this.options.eventTarget?.dispatchEvent(new Events.ActivityDestroyed(this)); - this.options.httpAgent.destroy?.(); this.logger?.debug(`Activity ${this.id} destroyed`); } @@ -172,7 +174,8 @@ export class Activity { const maxRetries = 5; const { id: activityId, agreementId } = this; const isRunning = () => this.isRunning; - const { activityExecuteTimeout, api, eventTarget } = this.options; + const { activityExecuteTimeout, eventTarget } = this.options; + const api = this.yagnaApi.activity; const handleError = this.handleError.bind(this); return new Readable({ objectMode: true, @@ -222,8 +225,8 @@ export class Activity { } private async streamingBatch(batchId, batchSize, startTime, timeout): Promise { - const basePath = this.options?.yagnaOptions?.basePath || this.options.api.control["configuration"]?.basePath; - const apiKey = this.options?.yagnaOptions?.apiKey || this.options.api.control["configuration"]?.apiKey; + const basePath = this.yagnaApi.yagnaOptions.basePath; + const apiKey = this.yagnaApi.yagnaOptions.apiKey; const eventSource = new EventSource(`${basePath}/activity/${this.id}/exec/${batchId}`, { headers: { Accept: "text/event-stream", @@ -279,13 +282,13 @@ export class Activity { throw error; } ++retryCount; - const failMsg = "There was an error retrieving activity results. "; + const failMsg = "There was an error retrieving activity results."; const errorMsg = error?.response?.data?.message || error?.message || error; if (retryCount < maxRetries) { this.logger?.debug(`${failMsg} Retrying in ${this.options.activityExeBatchResultsFetchInterval}.`); return retryCount; } else { - this.logger?.error(`${failMsg} Giving up after ${retryCount} attempts. ${errorMsg}`); + this.logger?.warn(`${failMsg} Giving up after ${retryCount} attempts. ${errorMsg}`); } throw new Error(`Command #${cmdIndex || 0} getExecBatchResults error: ${errorMsg}`); } @@ -317,7 +320,7 @@ export class Activity { private async isTerminated(): Promise<{ terminated: boolean; reason?: string; errorMessage?: string }> { try { - const { data } = await this.options.api.state.getActivityState(this.id); + const { data } = await this.yagnaApi.activity.state.getActivityState(this.id); const state = ActivityStateEnum[data?.state?.[0]]; return { terminated: state === ActivityStateEnum.Terminated, diff --git a/src/activity/config.ts b/src/activity/config.ts index a02becaf1..f4d4ed97d 100644 --- a/src/activity/config.ts +++ b/src/activity/config.ts @@ -1,9 +1,5 @@ import { ActivityOptions } from "./activity"; -import { yaActivity } from "ya-ts-client"; -import { RequestorControlApi, RequestorStateApi } from "ya-ts-client/dist/ya-activity/api"; -import { EnvUtils, Logger } from "../utils"; -import { YagnaOptions } from "../executor"; -import { Agent } from "http"; +import { Logger } from "../utils"; const DEFAULTS = { activityRequestTimeout: 10000, @@ -15,36 +11,18 @@ const DEFAULTS = { * @internal */ export class ActivityConfig { - public readonly api: { control: RequestorControlApi; state: RequestorStateApi }; public readonly activityRequestTimeout: number; public readonly activityExecuteTimeout: number; public readonly activityExeBatchResultsFetchInterval: number; public readonly logger?: Logger; public readonly eventTarget?: EventTarget; - public readonly yagnaOptions: YagnaOptions; - public readonly httpAgent: Agent; constructor(options?: ActivityOptions) { - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - this.httpAgent = new Agent({ keepAlive: true }); - const apiConfig = new yaActivity.Configuration({ - apiKey, - basePath: `${basePath}/activity-api/v1`, - accessToken: apiKey, - baseOptions: { httpAgent: this.httpAgent }, - }); - this.api = { - control: new RequestorControlApi(apiConfig), - state: new RequestorStateApi(apiConfig), - }; this.activityRequestTimeout = options?.activityRequestTimeout || DEFAULTS.activityRequestTimeout; this.activityExecuteTimeout = options?.activityExecuteTimeout || DEFAULTS.activityExecuteTimeout; this.activityExeBatchResultsFetchInterval = options?.activityExeBatchResultsFetchInterval || DEFAULTS.activityExeBatchResultsFetchInterval; this.logger = options?.logger; - this.yagnaOptions = { apiKey, basePath }; this.eventTarget = options?.eventTarget; } } diff --git a/src/activity/factory.ts b/src/activity/factory.ts index 47f7e41dd..d16fee21a 100644 --- a/src/activity/factory.ts +++ b/src/activity/factory.ts @@ -1,6 +1,7 @@ import { Activity, ActivityOptions } from "./activity"; import { ActivityConfig } from "./config"; import { Events } from "../events"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * Activity Factory @@ -14,10 +15,12 @@ export class ActivityFactory { * Creating ActivityFactory * * @param agreementId + * @param yagnaApi - {@link YagnaApi} * @param options - {@link ActivityOptions} */ constructor( private readonly agreementId: string, + private readonly yagnaApi: YagnaApi, options?: ActivityOptions, ) { this.options = new ActivityConfig(options); @@ -35,7 +38,7 @@ export class ActivityFactory { if (secure) { throw new Error("Not implemented"); } - return this.createActivity(this.agreementId, this.options); + return this.createActivity(); } catch (error) { const msg = `Unable to create activity: ${error?.response?.data?.message || error}`; this.options.logger?.error(msg); @@ -43,11 +46,11 @@ export class ActivityFactory { } } - private async createActivity(agreementId: string, options: ActivityConfig): Promise { - const { data } = await this.options.api.control.createActivity({ agreementId }); + private async createActivity(): Promise { + const { data } = await this.yagnaApi.activity.control.createActivity({ agreementId: this.agreementId }); const id = typeof data == "string" ? data : data.activityId; this.options.logger?.debug(`Activity ${id} created`); - this.options.eventTarget?.dispatchEvent(new Events.ActivityCreated({ id, agreementId })); - return new Activity(id, agreementId, options); + this.options.eventTarget?.dispatchEvent(new Events.ActivityCreated({ id, agreementId: this.agreementId })); + return new Activity(id, this.agreementId, this.yagnaApi, this.options); } } diff --git a/src/agreement/agreement.ts b/src/agreement/agreement.ts index 72b78088d..fa016cea7 100644 --- a/src/agreement/agreement.ts +++ b/src/agreement/agreement.ts @@ -4,6 +4,7 @@ import { YagnaOptions } from "../executor"; import { AgreementFactory } from "./factory"; import { AgreementConfig } from "./config"; import { Events } from "../events"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * @hidden @@ -52,12 +53,14 @@ export class Agreement { /** * @param id - agreement ID * @param provider - {@link ProviderInfo} + * @param yagnaApi - {@link YagnaApi} * @param options - {@link AgreementConfig} * @hidden */ constructor( public readonly id, public readonly provider: ProviderInfo, + private readonly yagnaApi: YagnaApi, private readonly options: AgreementConfig, ) { this.logger = options.logger; @@ -66,11 +69,12 @@ export class Agreement { /** * Create agreement for given proposal ID * @param proposalId - proposal ID + * @param yagnaApi * @param agreementOptions - {@link AgreementOptions} * @return Agreement */ - static async create(proposalId: string, agreementOptions?: AgreementOptions): Promise { - const factory = new AgreementFactory(agreementOptions); + static async create(proposalId: string, yagnaApi: YagnaApi, agreementOptions?: AgreementOptions): Promise { + const factory = new AgreementFactory(yagnaApi, agreementOptions); return factory.create(proposalId); } @@ -78,7 +82,9 @@ export class Agreement { * Refresh agreement details */ async refreshDetails() { - const { data } = await this.options.api.getAgreement(this.id, { timeout: this.options.agreementRequestTimeout }); + const { data } = await this.yagnaApi.market.getAgreement(this.id, { + timeout: this.options.agreementRequestTimeout, + }); this.agreementData = data; } @@ -98,8 +104,8 @@ export class Agreement { */ async confirm() { try { - await this.options.api.confirmAgreement(this.id); - await this.options.api.waitForApproval(this.id, this.options.agreementWaitingForApprovalTimeout); + await this.yagnaApi.market.confirmAgreement(this.id); + await this.yagnaApi.market.waitForApproval(this.id, this.options.agreementWaitingForApprovalTimeout); this.logger?.debug(`Agreement ${this.id} approved`); this.options.eventTarget?.dispatchEvent( new Events.AgreementConfirmed({ id: this.id, providerId: this.provider.id }), @@ -133,7 +139,7 @@ export class Agreement { if ((await this.getState()) !== AgreementStateEnum.Terminated) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore TODO: API binding BUG with reason type - await this.options.api.terminateAgreement(this.id, reason, { + await this.yagnaApi.market.terminateAgreement(this.id, reason, { timeout: this.options.agreementRequestTimeout, }); this.options.eventTarget?.dispatchEvent( @@ -144,8 +150,6 @@ export class Agreement { throw new Error( `Unable to terminate agreement ${this.id}. ${error.response?.data?.message || error.response?.data || error}`, ); - } finally { - this.options.httpAgent.destroy?.(); } } } diff --git a/src/agreement/config.ts b/src/agreement/config.ts index 306b9ed18..9cbeeb9a4 100644 --- a/src/agreement/config.ts +++ b/src/agreement/config.ts @@ -1,10 +1,7 @@ import { AgreementOptions } from "./agreement"; import { AgreementSelector, AgreementServiceOptions } from "./service"; -import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { Configuration } from "ya-ts-client/dist/ya-market"; -import { EnvUtils, Logger } from "../utils"; +import { Logger } from "../utils"; import { randomAgreementSelector } from "./strategy"; -import { Agent } from "http"; const DEFAULTS = { agreementRequestTimeout: 30000, @@ -18,23 +15,10 @@ const DEFAULTS = { export class AgreementConfig { readonly agreementRequestTimeout: number; readonly agreementWaitingForApprovalTimeout: number; - readonly api: RequestorApi; readonly logger?: Logger; readonly eventTarget?: EventTarget; - readonly httpAgent: Agent; constructor(public readonly options?: AgreementOptions) { - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - this.httpAgent = new Agent({ keepAlive: true }); - const apiConfig = new Configuration({ - apiKey, - basePath: `${basePath}/market-api/v1`, - accessToken: apiKey, - baseOptions: { httpAgent: this.httpAgent }, - }); - this.api = new RequestorApi(apiConfig); this.agreementRequestTimeout = options?.agreementRequestTimeout || DEFAULTS.agreementRequestTimeout; this.agreementWaitingForApprovalTimeout = options?.agreementWaitingForApprovalTimeout || DEFAULTS.agreementWaitingForApprovalTimeout; diff --git a/src/agreement/factory.ts b/src/agreement/factory.ts index e144095ec..d97906db6 100644 --- a/src/agreement/factory.ts +++ b/src/agreement/factory.ts @@ -2,6 +2,7 @@ import { Agreement, AgreementOptions } from "./agreement"; import { Logger } from "../utils"; import { AgreementConfig } from "./config"; import { Events } from "../events"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * AgreementFactory @@ -14,9 +15,13 @@ export class AgreementFactory { /** * Create AgreementFactory + * @param yagnaApi - {@link YagnaApi} * @param agreementOptions - {@link AgreementOptions} */ - constructor(agreementOptions?: AgreementOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + agreementOptions?: AgreementOptions, + ) { this.options = new AgreementConfig(agreementOptions); this.logger = agreementOptions?.logger; } @@ -32,16 +37,16 @@ export class AgreementFactory { proposalId, validTo: new Date(+new Date() + 3600 * 1000).toISOString(), }; - const { data: agreementId } = await this.options.api.createAgreement(agreementProposalRequest, { + const { data: agreementId } = await this.yagnaApi.market.createAgreement(agreementProposalRequest, { timeout: this.options.agreementRequestTimeout, }); - const { data } = await this.options.api.getAgreement(agreementId); + const { data } = await this.yagnaApi.market.getAgreement(agreementId); const provider = { name: data?.offer.properties["golem.node.id.name"], id: data?.offer.providerId, }; if (!provider.id || !provider.name) throw new Error("Unable to get provider info"); - const agreement = new Agreement(agreementId, provider, this.options); + const agreement = new Agreement(agreementId, provider, this.yagnaApi, this.options); this.options.eventTarget?.dispatchEvent( new Events.AgreementCreated({ id: agreementId, diff --git a/src/agreement/service.ts b/src/agreement/service.ts index b1754611e..1372ec2dc 100644 --- a/src/agreement/service.ts +++ b/src/agreement/service.ts @@ -2,8 +2,9 @@ import Bottleneck from "bottleneck"; import { Logger } from "../utils"; import { Agreement, AgreementOptions, AgreementStateEnum } from "./agreement"; import { AgreementServiceConfig } from "./config"; -import { Proposal, ProposalDTO } from "../market/proposal"; +import { Proposal, ProposalDTO } from "../market"; import sleep from "../utils/sleep"; +import { YagnaApi } from "../utils/yagna/yagna"; export interface AgreementDTO { id: string; @@ -36,10 +37,12 @@ export class AgreementPoolService { private agreements = new Map(); private isServiceRunning = false; - private initialTime = Date.now(); private limiter: Bottleneck; - constructor(private readonly agreementServiceOptions?: AgreementServiceOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + agreementServiceOptions?: AgreementServiceOptions, + ) { this.config = new AgreementServiceConfig(agreementServiceOptions); this.logger = agreementServiceOptions?.logger; @@ -53,7 +56,6 @@ export class AgreementPoolService { */ async run() { this.isServiceRunning = true; - this.initialTime = +new Date(); this.logger?.debug("Agreement Pool Service has started"); } @@ -148,7 +150,6 @@ export class AgreementPoolService { async end() { this.isServiceRunning = false; await this.terminateAll({ message: "All computations done" }); - this.config.httpAgent.destroy?.(); this.logger?.debug("Agreement Pool Service has been stopped"); } @@ -172,7 +173,7 @@ export class AgreementPoolService { async createAgreement(candidate) { try { - let agreement = await Agreement.create(candidate.proposal.id, this.config.options); + let agreement = await Agreement.create(candidate.proposal.id, this.yagnaApi, this.config.options); agreement = await this.waitForAgreementApproval(agreement); const state = await agreement.getState(); @@ -206,7 +207,7 @@ export class AgreementPoolService { this.logger?.debug(`Agreement proposed to provider '${agreement.provider.name}'`); } - await this.config.api.waitForApproval(agreement.id, this.config.agreementWaitingForApprovalTimeout); + await this.yagnaApi.market.waitForApproval(agreement.id, this.config.agreementWaitingForApprovalTimeout); return agreement; } } diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 61d0796f2..d2a722c79 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -5,7 +5,7 @@ import { Task, TaskQueue, TaskService, Worker } from "../task"; import { PaymentService, PaymentOptions } from "../payment"; import { NetworkService } from "../network"; import { Result } from "../activity"; -import { sleep, Logger, LogLevel, runtimeContextChecker } from "../utils"; +import { sleep, Logger, LogLevel, runtimeContextChecker, Yagna } from "../utils"; import { StorageProvider, GftpStorageProvider, NullStorageProvider, WebSocketBrowserStorageProvider } from "../storage"; import { ExecutorConfig } from "./config"; import { Events } from "../events"; @@ -82,6 +82,7 @@ export class TaskExecutor { private isRunning = true; private configOptions: ExecutorOptions; private isCanceled = false; + private yagna: Yagna; /** * Create a new Task Executor @@ -129,11 +130,13 @@ export class TaskExecutor { this.configOptions = (typeof options === "string" ? { package: options } : options) as ExecutorOptions; this.options = new ExecutorConfig(this.configOptions); this.logger = this.options.logger; + this.yagna = new Yagna(this.configOptions.yagnaOptions); + const yagnaApi = this.yagna.getApi(); this.taskQueue = new TaskQueue>(); - this.agreementPoolService = new AgreementPoolService(this.options); - this.paymentService = new PaymentService(this.options); - this.marketService = new MarketService(this.agreementPoolService, this.options); - this.networkService = this.options.networkIp ? new NetworkService(this.options) : undefined; + this.agreementPoolService = new AgreementPoolService(yagnaApi, this.options); + this.paymentService = new PaymentService(yagnaApi, this.options); + this.marketService = new MarketService(this.agreementPoolService, yagnaApi, this.options); + this.networkService = this.options.networkIp ? new NetworkService(yagnaApi, this.options) : undefined; // Initialize storage provider. if (this.configOptions.storageProvider) { @@ -141,18 +144,13 @@ export class TaskExecutor { } else if (runtimeContextChecker.isNode) { this.storageProvider = new GftpStorageProvider(this.logger); } else if (runtimeContextChecker.isBrowser) { - this.storageProvider = new WebSocketBrowserStorageProvider({ - yagnaOptions: { - apiKey: this.options.yagnaOptions.apiKey, - basePath: this.options.yagnaOptions.basePath, - }, - logger: this.logger, - }); + this.storageProvider = new WebSocketBrowserStorageProvider(yagnaApi, this.options); } else { this.storageProvider = new NullStorageProvider(); } this.taskService = new TaskService( + this.yagna.getApi(), this.taskQueue, this.agreementPoolService, this.paymentService, @@ -168,6 +166,12 @@ export class TaskExecutor { * @description Method responsible initialize all executor services. */ async init() { + try { + await this.yagna.connect(); + } catch (error) { + this.logger?.error(error); + throw error; + } const manifest = this.options.packageOptions.manifest; const packageReference = this.options.package; let taskPackage: Package; @@ -184,7 +188,9 @@ export class TaskExecutor { taskPackage = packageReference; } } else { - throw new Error("No package or manifest provided"); + const error = new Error("No package or manifest provided"); + this.logger?.error(error); + throw error; } } @@ -215,6 +221,7 @@ export class TaskExecutor { await this.networkService?.end(); await Promise.all([this.taskService.end(), this.agreementPoolService.end(), this.marketService.end()]); await this.paymentService.end(); + await this.yagna.end(); this.options.eventTarget?.dispatchEvent(new Events.ComputationFinished()); this.printStats(); await this.statsService.end(); diff --git a/src/index.ts b/src/index.ts index 2546744ed..b128304d6 100755 --- a/src/index.ts +++ b/src/index.ts @@ -13,3 +13,4 @@ export { Package, PackageOptions } from "./package"; export { PaymentFilters } from "./payment"; export { Events, BaseEvent, EventType } from "./events"; export { Logger, LogLevel, jsonLogger, nullLogger, consoleLogger, pinoLogger, defaultLogger } from "./utils"; +export { Yagna } from "./utils/yagna/yagna"; diff --git a/src/market/config.ts b/src/market/config.ts index e81b97e4a..5231ef3e3 100644 --- a/src/market/config.ts +++ b/src/market/config.ts @@ -1,10 +1,7 @@ import { DemandOptions } from "./demand"; -import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; -import { Configuration } from "ya-ts-client/dist/ya-market"; import { EnvUtils, Logger } from "../utils"; import { MarketOptions, ProposalFilter } from "./service"; import { YagnaOptions } from "../executor"; -import { Agent } from "http"; import { acceptAllProposalFilter } from "./strategy"; const DEFAULTS = { @@ -21,7 +18,6 @@ const DEFAULTS = { * @internal */ export class DemandConfig { - public readonly api: RequestorApi; public readonly yagnaOptions?: YagnaOptions; public readonly timeout: number; public readonly expiration: number; @@ -30,21 +26,8 @@ export class DemandConfig { public readonly offerFetchingInterval: number; public readonly logger?: Logger; public readonly eventTarget?: EventTarget; - public readonly httpAgent: Agent; constructor(options?: DemandOptions) { - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - this.httpAgent = new Agent({ keepAlive: true }); - const apiConfig = new Configuration({ - apiKey, - basePath: `${basePath}/market-api/v1`, - accessToken: apiKey, - baseOptions: { httpAgent: this.httpAgent }, - }); - this.yagnaOptions = options?.yagnaOptions; - this.api = new RequestorApi(apiConfig); this.subnetTag = options?.subnetTag || EnvUtils.getYagnaSubnet() || DEFAULTS.subnetTag; this.timeout = options?.marketTimeout || DEFAULTS.marketTimeout; this.expiration = options?.marketOfferExpiration || DEFAULTS.marketOfferExpiration; diff --git a/src/market/demand.ts b/src/market/demand.ts index 8160d5123..51846e1b3 100644 --- a/src/market/demand.ts +++ b/src/market/demand.ts @@ -3,7 +3,7 @@ import { Allocation } from "../payment"; import { YagnaOptions } from "../executor"; import { DemandFactory } from "./factory"; import { Proposal } from "./proposal"; -import { Logger, sleep } from "../utils"; +import { Logger, sleep, YagnaApi } from "../utils"; import { DemandConfig } from "./config"; import { Events } from "../events"; import { ProposalEvent, ProposalRejectedEvent } from "ya-ts-client/dist/ya-market/src/models"; @@ -49,24 +49,32 @@ export class Demand extends EventTarget { * Create demand for given taskPackage * Note: it is an "atomic" operation, ie. as soon as Demand is created, the subscription is published on the market. * @param taskPackage - {@link Package} - * @param allocations - {@link Allocation} + * @param allocation - {@link Allocation} + * @param yagnaApi - {@link YagnaApi} * @param options - {@link DemandOptions} * @return Demand */ - static async create(taskPackage: Package, allocation: Allocation, options?: DemandOptions): Promise { - const factory = new DemandFactory(taskPackage, allocation, options); + static async create( + taskPackage: Package, + allocation: Allocation, + yagnaApi: YagnaApi, + options?: DemandOptions, + ): Promise { + const factory = new DemandFactory(taskPackage, allocation, yagnaApi, options); return factory.create(); } /** * @param id - demand ID * @param demandRequest - {@link DemandOfferBase} + * @param yagnaApi - {@link YagnaApi} * @param options - {@link DemandConfig} * @hidden */ constructor( public readonly id: string, private demandRequest: DemandOfferBase, + private yagnaApi: YagnaApi, private options: DemandConfig, ) { super(); @@ -79,9 +87,8 @@ export class Demand extends EventTarget { */ async unsubscribe() { this.isRunning = false; - await this.options.api.unsubscribeDemand(this.id); + await this.yagnaApi.market.unsubscribeDemand(this.id); this.options.eventTarget?.dispatchEvent(new events.DemandUnsubscribed({ id: this.id })); - this.options.httpAgent.destroy?.(); this.logger?.debug(`Demand ${this.id} unsubscribed`); } @@ -102,7 +109,7 @@ export class Demand extends EventTarget { private async subscribe() { while (this.isRunning) { try { - const { data: events } = await this.options.api.collectOffers( + const { data: events } = await this.yagnaApi.market.collectOffers( this.id, this.options.offerFetchingInterval / 1000, this.options.maxOfferEvents, @@ -126,7 +133,7 @@ export class Demand extends EventTarget { this.id, event.proposal.state === "Draft" ? this.findParentProposal(event.proposal.prevProposalId) : null, this.setCounteringProposalReference.bind(this), - this.options.api, + this.yagnaApi.market, event.proposal, this.demandRequest, this.options.eventTarget, diff --git a/src/market/factory.ts b/src/market/factory.ts index ab699d010..2145ce77a 100644 --- a/src/market/factory.ts +++ b/src/market/factory.ts @@ -4,6 +4,7 @@ import { Demand, DemandOptions } from "./demand"; import { DemandConfig } from "./config"; import * as events from "../events/events"; import { DecorationsBuilder, MarketDecoration } from "./builder"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * @internal @@ -12,8 +13,9 @@ export class DemandFactory { private options: DemandConfig; constructor( - private taskPackage: Package, - private allocation: Allocation, + private readonly taskPackage: Package, + private readonly allocation: Allocation, + private readonly yagnaApi: YagnaApi, options?: DemandOptions, ) { this.options = new DemandConfig(options); @@ -22,7 +24,7 @@ export class DemandFactory { async create(): Promise { const decorations = await this.getDecorations(); const demandRequest = new DecorationsBuilder().addDecorations(decorations).getDemandRequest(); - const { data: id } = await this.options.api.subscribeDemand(demandRequest).catch((e) => { + const { data: id } = await this.yagnaApi.market.subscribeDemand(demandRequest).catch((e) => { const reason = e.response?.data?.message || e.toString(); this.options.eventTarget?.dispatchEvent(new events.DemandFailed({ reason })); throw new Error(`Could not publish demand on the market. ${reason}`); @@ -34,7 +36,7 @@ export class DemandFactory { }), ); this.options.logger?.info(`Demand published on the market`); - return new Demand(id, demandRequest, this.options); + return new Demand(id, demandRequest, this.yagnaApi, this.options); } private async getDecorations(): Promise { diff --git a/src/market/service.ts b/src/market/service.ts index 06c46be04..559200a02 100644 --- a/src/market/service.ts +++ b/src/market/service.ts @@ -5,6 +5,7 @@ import { AgreementPoolService } from "../agreement"; import { Allocation } from "../payment"; import { Demand, DemandEvent, DemandEventType, DemandOptions } from "./demand"; import { MarketConfig } from "./config"; +import { YagnaApi } from "../utils/yagna/yagna"; export type ProposalFilter = (proposal: ProposalDTO) => Promise; @@ -30,6 +31,7 @@ export class MarketService { constructor( private readonly agreementPoolService: AgreementPoolService, + private readonly yagnaApi: YagnaApi, options?: MarketOptions, ) { this.options = new MarketConfig(options); @@ -48,13 +50,12 @@ export class MarketService { this.demand.removeEventListener(DemandEventType, this.demandEventListener.bind(this)); await this.demand.unsubscribe().catch((e) => this.logger?.error(`Could not unsubscribe demand. ${e}`)); } - this.options.httpAgent.destroy?.(); this.logger?.debug("Market Service has been stopped"); } private async createDemand(): Promise { if (!this.taskPackage || !this.allocation) throw new Error("The service has not been started correctly."); - this.demand = await Demand.create(this.taskPackage, this.allocation, this.options); + this.demand = await Demand.create(this.taskPackage, this.allocation, this.yagnaApi, this.options); this.demand.addEventListener(DemandEventType, this.demandEventListener.bind(this)); this.logger?.debug(`New demand has been created (${this.demand.id})`); return true; @@ -77,7 +78,7 @@ export class MarketService { private async resubscribeDemand() { if (this.demand) { this.demand.removeEventListener(DemandEventType, this.demandEventListener.bind(this)); - await this.demand.unsubscribe().catch((e) => this.logger?.error(`Could not unsubscribe demand. ${e}`)); + await this.demand.unsubscribe().catch((e) => this.logger?.debug(`Could not unsubscribe demand. ${e}`)); } let attempt = 1; let success = false; diff --git a/src/network/config.ts b/src/network/config.ts index f4aebcec1..64b007ee3 100644 --- a/src/network/config.ts +++ b/src/network/config.ts @@ -1,8 +1,5 @@ import { NetworkOptions } from "./network"; -import { RequestorApi } from "ya-ts-client/dist/ya-net/api"; -import { Configuration } from "ya-ts-client/dist/ya-payment"; -import { Agent } from "http"; -import { EnvUtils, Logger } from "../utils"; +import { Logger } from "../utils"; const DEFAULTS = { networkIp: "192.168.0.0/24", @@ -12,29 +9,14 @@ const DEFAULTS = { * @internal */ export class NetworkConfig { - public readonly api: RequestorApi; public readonly mask?: string; public readonly ip: string; public readonly ownerId: string; public readonly ownerIp?: string; public readonly gateway?: string; public readonly logger?: Logger; - public readonly apiUrl: string; - public readonly httpAgent: Agent; constructor(options: NetworkOptions) { - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - this.apiUrl = `${basePath}/net-api/v1`; - this.httpAgent = new Agent({ keepAlive: true }); - const apiConfig = new Configuration({ - apiKey, - basePath: this.apiUrl, - accessToken: apiKey, - baseOptions: { httpAgent: this.httpAgent }, - }); - this.api = new RequestorApi(apiConfig); this.ip = options?.networkIp || DEFAULTS.networkIp; this.mask = options?.networkMask; this.ownerId = options.networkOwnerId; diff --git a/src/network/identity.ts b/src/network/identity.ts deleted file mode 100644 index 885500ecd..000000000 --- a/src/network/identity.ts +++ /dev/null @@ -1,23 +0,0 @@ -// TODO: replace with a proper REST API client once ya-client and ya-ts-client are updated -// https://github.com/golemfactory/golem-js/issues/290 -import axios from "axios"; -import { YagnaOptions } from "../executor"; -import { EnvUtils } from "../utils"; - -/** - * A helper function to get the requestor's ID - * @param options - * @return requestorId - */ -export const getIdentity = async (options?: { yagnaOptions?: YagnaOptions }): Promise => { - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - const apiUrl = `${basePath}/me`; - const { - data: { identity }, - } = await axios.get(apiUrl, { - headers: { authorization: `Bearer ${apiKey}` }, - }); - return identity; -}; diff --git a/src/network/network.ts b/src/network/network.ts index 878ef8fca..d067626f0 100644 --- a/src/network/network.ts +++ b/src/network/network.ts @@ -3,6 +3,7 @@ import { Logger } from "../utils"; import { YagnaOptions } from "../executor"; import { NetworkConfig } from "./config"; import { NetworkNode } from "./node"; +import { YagnaApi } from "../utils/yagna/yagna"; /** * @hidden @@ -51,22 +52,23 @@ export class Network { /** * Create a new VPN. * + * @param yagnaApi - {@link YagnaApi} * @param options - {@link NetworkOptions} */ - static async create(options: NetworkOptions): Promise { + static async create(yagnaApi: YagnaApi, options: NetworkOptions): Promise { const config = new NetworkConfig(options); try { const { data: { id, ip, mask }, - } = await config.api.createNetwork({ + } = await yagnaApi.net.createNetwork({ id: config.ownerId, ip: config.ip, mask: config.mask, gateway: config.gateway, }); - const network = new Network(id!, config); + const network = new Network(id!, yagnaApi, config); await network.addNode(network.ownerId, network.ownerIp.toString()).catch(async (e) => { - await config.api.removeNetwork(id as string); + await yagnaApi.net.removeNetwork(id as string); throw e; }); config.logger?.info(`Network created: ID: ${id}, IP: ${ip}, Mask: ${mask}`); @@ -78,12 +80,14 @@ export class Network { /** * @param id + * @param yagnaApi * @param config * @private * @hidden */ private constructor( public readonly id: string, + private readonly yagnaApi: YagnaApi, public readonly config: NetworkConfig, ) { this.ipRange = IPv4CidrRange.fromCidr(config.mask ? `${config.ip}/${config.mask}` : config.ip); @@ -130,7 +134,7 @@ export class Network { } const node = new NetworkNode(nodeId, ipv4, this.getNetworkInfo.bind(this), this.getUrl()); this.nodes.set(nodeId, node); - await this.config.api.addNode(this.id, { id: nodeId, ip: ipv4.toString() }); + await this.yagnaApi.net.addNode(this.id, { id: nodeId, ip: ipv4.toString() }); this.logger?.debug(`Node has added to the network. ID: ${nodeId}, IP: ${ipv4.toString()}`); return node; } @@ -140,13 +144,11 @@ export class Network { */ async remove(): Promise { try { - await this.config.api.removeNetwork(this.id); + await this.yagnaApi.net.removeNetwork(this.id); } catch (error) { if (error.status === 404) this.logger?.warn(`Tried removing a network which doesn't exist. Network ID: ${this.id}`); return false; - } finally { - this.config.httpAgent.destroy?.(); } this.logger?.info(`Network has removed: ID: ${this.id}, IP: ${this.ip}`); return true; @@ -183,6 +185,6 @@ export class Network { } private getUrl() { - return this.config.apiUrl; + return this.yagnaApi.net["basePath"]; } } diff --git a/src/network/service.ts b/src/network/service.ts index f3c9b1159..bda841620 100644 --- a/src/network/service.ts +++ b/src/network/service.ts @@ -1,8 +1,7 @@ -import { Logger } from "../utils/index"; +import { Logger, YagnaApi } from "../utils"; import { Network } from "./index"; import { NetworkOptions } from "./network"; import { NetworkNode } from "./node"; -import { getIdentity } from "./identity"; export type NetworkServiceOptions = Omit; @@ -15,13 +14,19 @@ export class NetworkService { private network?: Network; private logger?: Logger; - constructor(private options?: NetworkServiceOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + private readonly options?: NetworkServiceOptions, + ) { this.logger = options?.logger; } async run(networkOwnerId?: string) { - if (!networkOwnerId) networkOwnerId = await getIdentity(this.options); - this.network = await Network.create({ ...this.options, networkOwnerId }); + if (!networkOwnerId) { + const { data } = await this.yagnaApi.identity.getIdentity(); + networkOwnerId = data.identity; + } + this.network = await Network.create(this.yagnaApi, { ...this.options, networkOwnerId }); this.logger?.debug("Network Service has started"); } diff --git a/src/package/repo_resolver.ts b/src/package/repo_resolver.ts deleted file mode 100644 index f9a0e9c3e..000000000 --- a/src/package/repo_resolver.ts +++ /dev/null @@ -1,92 +0,0 @@ -// ? This file is legacy, we should remove it or refactor it - -// import axios from "axios"; -// import { Logger, runtimeContextChecker } from "../utils"; - -// const FALLBACK_REPO_URL = "https://girepo.dev.golem.network"; -// // const PUBLIC_DNS_URL = "https://dns.google/resolve?type=srv&name="; -// const DEFAULT_REPO_SRV = "_girepo._tcp.dev.golem.network"; -// const SCHEMA = "https"; -// const TIMEOUT = 10000; - -// /** -// * @internal -// */ -// export interface RepoResolverOptions { -// logger?: Logger; -// } - -// /** -// * @internal -// */ -// export class RepoResolver { -// private constructor(private logger?: Logger) {} - -// static create({ logger }: RepoResolverOptions): RepoResolver { -// return new RepoResolver(logger); -// } -// private async isRecordValid(url) { -// try { -// await axios.head(url, { timeout: TIMEOUT }); -// return true; -// } catch (e) { -// if (e?.response?.status > 200 && e?.response?.status < 500) return true; -// this.logger?.warn(`Url ${url} is not responding. ${e?.message}`); -// return false; -// } -// } - -// async resolveRepoUrl() { -// try { -// const records = runtimeContextChecker.isBrowser -// ? await this.resolveRepoUrlForBrowser() -// : await this.resolveRepoUrlForNode(); - -// while (records.length > 0) { -// const url = records.splice((records.length * Math.random()) | 0, 1)[0]; -// if (await this.isRecordValid(url)) { -// return url; -// } -// } -// } catch (e) { -// this.logger?.warn(`Error occurred while trying to get SRV record : ${e}`); -// } -// return null; -// } - -// async getRepoUrl() { -// const repoUrl = await this.resolveRepoUrl(); -// if (repoUrl) { -// this.logger?.debug(`Using image repository: ${repoUrl}.`); -// return repoUrl; -// } -// this.logger?.warn(`Problem resolving image repository: ${DEFAULT_REPO_SRV}, falling back to ${FALLBACK_REPO_URL}.`); -// return FALLBACK_REPO_URL; -// } - -// private async resolveRepoUrlForBrowser(): Promise { -// return [FALLBACK_REPO_URL]; -// } - -// <<<<<<< HEAD -// private async resolveRepoUrlForNode() { -// //to be able to run against other server ( like stage registry ) -// if (process.env.REPO_URL) { -// return [process.env.REPO_URL]; -// } - -// ======= -// private async resolveRepoUrlForNode(): Promise { -// >>>>>>> master -// return new Promise((resolve, reject) => { -// import("dns") -// .then((nodeDns) => { -// nodeDns.resolveSrv(DEFAULT_REPO_SRV, (err, addresses) => { -// if (err) reject(err); -// resolve(addresses?.map((a) => (a.name && a.port ? `${SCHEMA}://${a.name}:${a.port}` : ""))); -// }); -// }) -// .catch((err) => reject(err)); -// }); -// } -// } diff --git a/src/payment/allocation.ts b/src/payment/allocation.ts index e4d5a6789..3348a48fb 100644 --- a/src/payment/allocation.ts +++ b/src/payment/allocation.ts @@ -2,6 +2,7 @@ import { Allocation as Model, MarketDecoration } from "ya-ts-client/dist/ya-paym import { AllocationConfig, BasePaymentOptions } from "./config"; import { Allocation as AllocationModel } from "ya-ts-client/dist/ya-payment/src/models/allocation"; import { Events } from "../events"; +import { YagnaApi } from "../utils"; /** * @hidden @@ -34,9 +35,10 @@ export class Allocation { /** * Create allocation * + * @param yagnaApi - {@link YagnaApi} * @param options - {@link AllocationOptions} */ - static async create(options: AllocationOptions): Promise { + static async create(yagnaApi: YagnaApi, options: AllocationOptions): Promise { const config = new AllocationConfig(options); const now = new Date(); const model: AllocationModel = { @@ -50,7 +52,7 @@ export class Allocation { spentAmount: "", allocationId: "", }; - const { data: newModel } = await config.api.createAllocation(model).catch((error) => { + const { data: newModel } = await yagnaApi.payment.createAllocation(model).catch((error) => { throw new Error( `Could not create new allocation. ${error.response?.data?.message || error.response?.data || error}`, ); @@ -65,16 +67,18 @@ export class Allocation { config.logger?.debug( `Allocation ${newModel.allocationId} has been created for addrress ${config.account.address} using payment platform ${config.account.platform}`, ); - return new Allocation(config, newModel); + return new Allocation(yagnaApi, config, newModel); } /** + * @param yagnaApi - {@link YagnaApi} * @param options - {@link AllocationConfig} * @param model - {@link Model} * @hidden */ constructor( - private options: AllocationConfig, + private readonly yagnaApi: YagnaApi, + private readonly options: AllocationConfig, model: Model, ) { this.id = model.allocationId; @@ -112,12 +116,9 @@ export class Allocation { * Release allocation */ async release() { - await this.options.api - .releaseAllocation(this.id) - .catch((e) => { - throw new Error(`Could not release allocation. ${e.response?.data?.message || e}`); - }) - .finally(() => this.options.httpAgent.destroy?.()); + await this.yagnaApi.payment.releaseAllocation(this.id).catch((e) => { + throw new Error(`Could not release allocation. ${e.response?.data?.message || e}`); + }); this.options?.logger?.debug(`Allocation ${this.id} has been released.`); } @@ -127,14 +128,14 @@ export class Allocation { * @return {@link MarketDecoration} */ async getDemandDecoration(): Promise { - const { data: decoration } = await this.options.api.getDemandDecorations([this.id]).catch((e) => { + const { data: decoration } = await this.yagnaApi.payment.getDemandDecorations([this.id]).catch((e) => { throw new Error(`Unable to get demand decorations. ${e.response?.data?.message || e}`); }); return decoration; } private async refresh() { - const { data } = await this.options.api.getAllocation(this.id).catch((e) => { + const { data } = await this.yagnaApi.payment.getAllocation(this.id).catch((e) => { throw new Error(`Could not get allocation data. ${e.response?.data || e}`); }); this.remainingAmount = data.remainingAmount; diff --git a/src/payment/config.ts b/src/payment/config.ts index 476f328a9..84e49914a 100644 --- a/src/payment/config.ts +++ b/src/payment/config.ts @@ -1,12 +1,9 @@ import { AllocationOptions } from "./allocation"; -import { Configuration } from "ya-ts-client/dist/ya-payment"; -import { RequestorApi } from "ya-ts-client/dist/ya-payment/api"; import { EnvUtils, Logger } from "../utils"; import { YagnaOptions } from "../executor"; import { DebitNoteFilter, InvoiceFilter, PaymentOptions } from "./service"; import { InvoiceOptions } from "./invoice"; import { acceptAllDebitNotesFilter, acceptAllInvoicesFilter } from "./strategy"; -import { Agent } from "http"; const DEFAULTS = Object.freeze({ payment: { network: "goerli", driver: "erc20" }, @@ -35,27 +32,12 @@ export interface BasePaymentOptions { * @internal */ abstract class BaseConfig { - public readonly yagnaOptions?: YagnaOptions; public readonly paymentTimeout: number; - public readonly api: RequestorApi; public readonly logger?: Logger; public readonly eventTarget?: EventTarget; public readonly payment: { driver: string; network: string }; - public readonly httpAgent: Agent; - constructor(public readonly options?: BasePaymentOptions) { - this.yagnaOptions = options?.yagnaOptions; - const apiKey = options?.yagnaOptions?.apiKey || EnvUtils.getYagnaAppKey(); - if (!apiKey) throw new Error("Api key not defined"); - const basePath = options?.yagnaOptions?.basePath || EnvUtils.getYagnaApiUrl(); - this.httpAgent = new Agent({ keepAlive: true }); - const apiConfig = new Configuration({ - apiKey, - basePath: `${basePath}/payment-api/v1`, - accessToken: apiKey, - baseOptions: { httpAgent: this.httpAgent }, - }); - this.api = new RequestorApi(apiConfig); + constructor(options?: BasePaymentOptions) { this.paymentTimeout = options?.paymentTimeout || DEFAULTS.paymentTimeout; this.payment = { driver: options?.payment?.driver || DEFAULTS.payment.driver, @@ -115,5 +97,3 @@ export class InvoiceConfig extends BaseConfig { super(options); } } - -export class AccountConfig extends BaseConfig {} diff --git a/src/payment/debit_note.ts b/src/payment/debit_note.ts index 0ad6f69c4..8a972ce54 100644 --- a/src/payment/debit_note.ts +++ b/src/payment/debit_note.ts @@ -3,6 +3,7 @@ import { DebitNote as Model } from "ya-ts-client/dist/ya-payment/src/models"; import { BaseNote } from "./invoice"; import { Events } from "../events"; import { Rejection } from "./rejection"; +import { YagnaApi } from "../utils"; export type InvoiceOptions = BasePaymentOptions; @@ -31,23 +32,26 @@ export class DebitNote extends BaseNote { * Create Debit Note Model * * @param debitNoteId - debit note id + * @param yagnaApi - {@link YagnaApi} * @param options - {@link InvoiceOptions} */ - static async create(debitNoteId: string, options?: InvoiceOptions): Promise { + static async create(debitNoteId: string, yagnaApi: YagnaApi, options?: InvoiceOptions): Promise { const config = new InvoiceConfig(options); - const { data: model } = await config.api.getDebitNote(debitNoteId); - return new DebitNote(model, config); + const { data: model } = await yagnaApi.payment.getDebitNote(debitNoteId); + return new DebitNote(model, yagnaApi, config); } /** * * @param model + * @param yagnaApi * @param options * @protected * @hidden */ protected constructor( model: Model, + protected yagnaApi: YagnaApi, protected options: InvoiceConfig, ) { super(model, options); @@ -77,7 +81,7 @@ export class DebitNote extends BaseNote { */ async accept(totalAmountAccepted: string, allocationId: string) { try { - await this.options.api.acceptDebitNote(this.id, { totalAmountAccepted, allocationId }); + await this.yagnaApi.payment.acceptDebitNote(this.id, { totalAmountAccepted, allocationId }); } catch (e) { const reason = e?.response?.data?.message || e; this.options.eventTarget?.dispatchEvent( @@ -96,7 +100,7 @@ export class DebitNote extends BaseNote { async reject(rejection: Rejection) { try { // TODO: not implemented by yagna - 501 returned - // await this.options.api.rejectDebitNote(this.id, rejection); + // await this.yagnaApi.payment.rejectDebitNote(this.id, rejection); } catch (e) { throw new Error(`Unable to reject debit note ${this.id} ${e?.response?.data?.message || e}`); } finally { @@ -107,7 +111,7 @@ export class DebitNote extends BaseNote { } protected async refreshStatus() { - const { data: model } = await this.options.api.getDebitNote(this.id); + const { data: model } = await this.yagnaApi.payment.getDebitNote(this.id); this.status = model.status; } } diff --git a/src/payment/invoice.ts b/src/payment/invoice.ts index 40a7c3b99..c4cd84da0 100644 --- a/src/payment/invoice.ts +++ b/src/payment/invoice.ts @@ -2,6 +2,7 @@ import { BasePaymentOptions, InvoiceConfig } from "./config"; import { Invoice as Model, InvoiceStatus } from "ya-ts-client/dist/ya-payment/src/models"; import { Events } from "../events"; import { Rejection } from "./rejection"; +import { YagnaApi } from "../utils"; export type InvoiceOptions = BasePaymentOptions; @@ -89,22 +90,25 @@ export class Invoice extends BaseNote { * Create invoice using invoice ID * * @param invoiceId - Invoice ID + * @param yagnaApi - {@link YagnaApi} * @param options - {@link InvoiceOptions} */ - static async create(invoiceId: string, options?: InvoiceOptions): Promise { + static async create(invoiceId: string, yagnaApi: YagnaApi, options?: InvoiceOptions): Promise { const config = new InvoiceConfig(options); - const { data: model } = await config.api.getInvoice(invoiceId); - return new Invoice(model, config); + const { data: model } = await yagnaApi.payment.getInvoice(invoiceId); + return new Invoice(model, yagnaApi, config); } /** * @param model + * @param yagnaApi * @param options * @protected * @hidden */ protected constructor( model: Model, + protected yagnaApi: YagnaApi, protected options: InvoiceConfig, ) { super(model, options); @@ -149,7 +153,7 @@ export class Invoice extends BaseNote { */ async accept(totalAmountAccepted: string, allocationId: string) { try { - await this.options.api.acceptInvoice(this.id, { totalAmountAccepted, allocationId }); + await this.yagnaApi.payment.acceptInvoice(this.id, { totalAmountAccepted, allocationId }); } catch (e) { const reason = e?.response?.data?.message || e; this.options.eventTarget?.dispatchEvent( @@ -168,7 +172,7 @@ export class Invoice extends BaseNote { async reject(rejection: Rejection) { try { // TODO: not implemented by yagna !!!! - // await this.options.api.rejectInvoice(this.id, rejection); + // await this.yagnaApi.payment.rejectInvoice(this.id, rejection); } catch (e) { throw new Error(`Unable to reject invoice ${this.id} ${e?.response?.data?.message || e}`); } finally { @@ -179,7 +183,7 @@ export class Invoice extends BaseNote { } protected async refreshStatus() { - const { data: model } = await this.options.api.getInvoice(this.id); + const { data: model } = await this.yagnaApi.payment.getInvoice(this.id); this.status = model.status; } } diff --git a/src/payment/payments.ts b/src/payment/payments.ts index a1ff48048..785fb4bc5 100644 --- a/src/payment/payments.ts +++ b/src/payment/payments.ts @@ -1,5 +1,5 @@ import { BasePaymentOptions, PaymentConfig } from "./config"; -import { Logger, sleep } from "../utils"; +import { Logger, sleep, YagnaApi } from "../utils"; import { Invoice } from "./invoice"; import { DebitNote } from "./debit_note"; import { Events } from "../events"; @@ -19,11 +19,14 @@ export class Payments extends EventTarget { private logger?: Logger; private lastInvoiceFetchingTime: string = new Date().toISOString(); private lastDebitNotesFetchingTime: string = new Date().toISOString(); - static async create(options?: PaymentOptions) { - return new Payments(options); + static async create(yagnaApi: YagnaApi, options?: PaymentOptions) { + return new Payments(yagnaApi, new PaymentConfig(options)); } - constructor(options?: PaymentOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + options?: PaymentOptions, + ) { super(); this.options = new PaymentConfig(options); this.logger = this.options.logger; @@ -35,7 +38,6 @@ export class Payments extends EventTarget { */ async unsubscribe() { this.isRunning = false; - this.options.httpAgent.destroy?.(); this.logger?.debug(`Payments unsubscribed`); } @@ -51,7 +53,7 @@ export class Payments extends EventTarget { private async subscribeForInvoices() { while (this.isRunning) { try { - const { data: invoiceEvents } = await this.options.api.getInvoiceEvents( + const { data: invoiceEvents } = await this.yagnaApi.payment.getInvoiceEvents( this.options.invoiceFetchingInterval / 1000, this.lastInvoiceFetchingTime, this.options.maxInvoiceEvents, @@ -60,7 +62,7 @@ export class Payments extends EventTarget { ); for (const event of invoiceEvents) { if (event.eventType !== "InvoiceReceivedEvent") continue; - const invoice = await Invoice.create(event["invoiceId"], { ...this.options.options }).catch( + const invoice = await Invoice.create(event["invoiceId"], this.yagnaApi, { ...this.options }).catch( (e) => this.logger?.error( `Unable to create invoice ID: ${event["invoiceId"]}. ${e?.response?.data?.message || e}`, @@ -83,7 +85,7 @@ export class Payments extends EventTarget { private async subscribeForDebitNotes() { while (this.isRunning) { try { - const { data: debitNotesEvents } = await this.options.api + const { data: debitNotesEvents } = await this.yagnaApi.payment .getDebitNoteEvents( this.options.debitNotesFetchingInterval / 1000, this.lastDebitNotesFetchingTime, @@ -94,7 +96,7 @@ export class Payments extends EventTarget { .catch(() => ({ data: [] })); for (const event of debitNotesEvents) { if (event.eventType !== "DebitNoteReceivedEvent") continue; - const debitNote = await DebitNote.create(event["debitNoteId"], { ...this.options.options }).catch( + const debitNote = await DebitNote.create(event["debitNoteId"], this.yagnaApi, { ...this.options }).catch( (e) => this.logger?.error( `Unable to create debit note ID: ${event["debitNoteId"]}. ${e?.response?.data?.message || e}`, diff --git a/src/payment/service.ts b/src/payment/service.ts index e11d2503c..5200d7a48 100644 --- a/src/payment/service.ts +++ b/src/payment/service.ts @@ -1,11 +1,10 @@ -import { Logger, sleep } from "../utils"; +import { Logger, sleep, YagnaApi } from "../utils"; import { Allocation } from "./allocation"; import { BasePaymentOptions, PaymentConfig } from "./config"; import { Invoice, InvoiceDTO } from "./invoice"; import { DebitNote, DebitNoteDTO } from "./debit_note"; import { Payments, PaymentEventType, DebitNoteEvent, InvoiceEvent } from "./payments"; import { RejectionReason } from "./rejection"; -import { getIdentity } from "../network/identity"; export interface PaymentOptions extends BasePaymentOptions { /** Interval for checking new invoices */ @@ -45,13 +44,16 @@ export class PaymentService { private paidDebitNotes: Set = new Set(); private payments?: Payments; - constructor(options?: PaymentOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + options?: PaymentOptions, + ) { this.options = new PaymentConfig(options); this.logger = this.options.logger; } async run() { this.isRunning = true; - this.payments = await Payments.create(this.options); + this.payments = await Payments.create(this.yagnaApi, this.options); this.payments.addEventListener(PaymentEventType, this.subscribePayments.bind(this)); this.logger?.debug("Payment Service has started"); } @@ -77,7 +79,6 @@ export class PaymentService { this.payments?.unsubscribe().catch((error) => this.logger?.warn(error)); this.payments?.removeEventListener(PaymentEventType, this.subscribePayments.bind(this)); await this.allocation?.release().catch((error) => this.logger?.warn(error)); - this.options.httpAgent.destroy?.(); this.logger?.info("Allocation has been released"); this.logger?.debug("Payment service has been stopped"); } @@ -88,7 +89,7 @@ export class PaymentService { platform: this.getPaymentPlatform(), address: await this.getPaymentAddress(), }; - this.allocation = await Allocation.create({ ...this.options.options, account }); + this.allocation = await Allocation.create(this.yagnaApi, { ...this.options, account }); return this.allocation; } catch (error) { throw new Error( @@ -168,6 +169,7 @@ export class PaymentService { } private async getPaymentAddress(): Promise { - return getIdentity(this.options); + const { data } = await this.yagnaApi.identity.getIdentity(); + return data.identity; } } diff --git a/src/storage/ws-browser.ts b/src/storage/ws-browser.ts index a822b9846..cf760e729 100644 --- a/src/storage/ws-browser.ts +++ b/src/storage/ws-browser.ts @@ -1,15 +1,10 @@ import { StorageProvider, StorageProviderDataCallback } from "./provider"; import { v4 } from "uuid"; import { encode, toObject } from "flatbuffers/js/flexbuffers"; -import { getIdentity } from "../network/identity"; import * as jsSha3 from "js-sha3"; -import { Logger, nullLogger } from "../utils"; +import { Logger, nullLogger, YagnaApi } from "../utils"; export interface WebSocketStorageProviderOptions { - yagnaOptions: { - apiKey: string; - basePath: string; - }; logger?: Logger; } @@ -63,7 +58,10 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { private services = new Map(); private logger: Logger; - constructor(private readonly options: WebSocketStorageProviderOptions) { + constructor( + private readonly yagnaApi: YagnaApi, + private readonly options: WebSocketStorageProviderOptions, + ) { this.logger = options.logger ?? nullLogger(); } @@ -148,9 +146,8 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { private async createFileInfo(): Promise { const id = v4(); - const me = await getIdentity({ - yagnaOptions: this.options.yagnaOptions, - }); + const { data } = await this.yagnaApi.identity.getIdentity(); + const me = data.identity; return { id, @@ -169,44 +166,23 @@ export class WebSocketBrowserStorageProvider implements StorageProvider { } private async createService(fileInfo: GftpFileInfo, components: string[]): Promise { - const yagnaOptions = this.options.yagnaOptions; - const resp = await fetch(new URL("/gsb-api/v1/services", yagnaOptions.basePath), { - method: "POST", - headers: { - Authorization: `Bearer ${yagnaOptions.apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - listen: { - on: `/public/gftp/${fileInfo.id}`, - components, - }, - }), - }); + const resp = await this.yagnaApi.gsb.createService(fileInfo, components); if (resp.status !== 201) { throw new Error(`Invalid response: ${resp.status}`); } - const body = await resp.json(); - const messages_link = `/gsb-api/v1/services/${body.servicesId}?authToken=${yagnaOptions.apiKey}`; - const url = new URL(messages_link, this.options.yagnaOptions.basePath); + const servicesId = resp.data.serviceId; + const messages_link = `/gsb-api/v1/services/${servicesId}?authToken=${this.yagnaApi.yagnaOptions.apiKey}`; + const url = new URL(messages_link, this.yagnaApi.yagnaOptions.basePath); url.protocol = "ws:"; - this.services.set(fileInfo.url, body.servicesId); + this.services.set(fileInfo.url, servicesId); - return { url, serviceId: body.servicesId }; + return { url, serviceId: servicesId }; } private async deleteService(id: string): Promise { - const yagnaOptions = this.options.yagnaOptions; - const resp = await fetch(new URL(`/gsb-api/v1/services/${id}`, yagnaOptions.basePath), { - method: "DELETE", - headers: { - Authorization: `Bearer ${yagnaOptions.apiKey}`, - "Content-Type": "application/json", - }, - }); - + const resp = await this.yagnaApi.gsb.deleteService(id); if (resp.status !== 200) { throw new Error(`Invalid response: ${resp.status}`); } diff --git a/src/task/batch.spec.ts b/src/task/batch.spec.ts index 188f9614c..8fca4fada 100644 --- a/src/task/batch.spec.ts +++ b/src/task/batch.spec.ts @@ -2,7 +2,7 @@ import { DownloadFile, Run, UploadData, UploadFile } from "../script"; import { Batch } from "./batch"; import { NullStorageProvider } from "../storage"; import { ActivityMock } from "../../tests/mock/activity.mock"; -import { LoggerMock } from "../../tests/mock"; +import { LoggerMock, YagnaMock } from "../../tests/mock"; import { Result } from "../activity"; describe("Batch", () => { @@ -10,7 +10,7 @@ describe("Batch", () => { let batch: Batch; beforeEach(() => { - activity = new ActivityMock(); + activity = new ActivityMock("test_id", "test_id", new YagnaMock().getApi()); batch = new Batch(activity, new NullStorageProvider(), new LoggerMock()); }); diff --git a/src/task/service.ts b/src/task/service.ts index b95ec497a..6c7cad901 100644 --- a/src/task/service.ts +++ b/src/task/service.ts @@ -1,7 +1,7 @@ import { Task } from "./task"; import { TaskQueue } from "./queue"; import { WorkContext } from "./work"; -import { Logger, sleep } from "../utils"; +import { Logger, sleep, YagnaApi } from "../utils"; import { StorageProvider } from "../storage"; import { AgreementPoolService } from "../agreement"; import { PaymentService } from "../payment"; @@ -33,6 +33,7 @@ export class TaskService { private options: TaskConfig; constructor( + private yagnaApi: YagnaApi, private tasksQueue: TaskQueue>, private agreementPoolService: AgreementPoolService, private paymentService: PaymentService, @@ -79,7 +80,7 @@ export class TaskService { if (this.activities.has(agreement.id)) { activity = this.activities.get(agreement.id); } else { - activity = await Activity.create(agreement.id, this.options); + activity = await Activity.create(agreement.id, this.yagnaApi, this.options); this.activities.set(agreement.id, activity); } this.options.eventTarget?.dispatchEvent( @@ -110,7 +111,6 @@ export class TaskService { logger: this.logger, activityPreparingTimeout: this.options.activityPreparingTimeout, activityStateCheckingInterval: this.options.activityStateCheckingInterval, - isRunning: () => this.isRunning, }); await ctx.before(); if (initWorker && !this.initWorkersDone.has(activity.id)) { diff --git a/src/task/work.spec.ts b/src/task/work.spec.ts index 5f65972e9..6b80e1505 100644 --- a/src/task/work.spec.ts +++ b/src/task/work.spec.ts @@ -1,5 +1,5 @@ import { Batch, WorkContext } from "./index"; -import { LoggerMock } from "../../tests/mock"; +import { LoggerMock, YagnaMock } from "../../tests/mock"; import { ActivityStateEnum, ResultState } from "../activity"; import { DownloadData, DownloadFile, Run, Script, UploadData, UploadFile } from "../script"; import { ActivityMock } from "../../tests/mock/activity.mock"; @@ -13,10 +13,9 @@ describe("Work Context", () => { beforeEach(() => { logger.clear(); - activity = new ActivityMock(); + activity = new ActivityMock("test_id", "test_id", new YagnaMock().getApi()); context = new WorkContext(activity, { logger: logger, - isRunning: jest.fn(), }); }); diff --git a/src/task/work.ts b/src/task/work.ts index 0a8fb04da..dc90d2541 100644 --- a/src/task/work.ts +++ b/src/task/work.ts @@ -34,7 +34,6 @@ export interface WorkOptions { networkNode?: NetworkNode; logger?: Logger; initWorker?: Worker; - isRunning: () => boolean; } interface CommandOptions { diff --git a/src/utils/index.ts b/src/utils/index.ts index f521ac6be..5e447932f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,3 +8,4 @@ export { jsonLogger } from "./logger/jsonLogger"; export { nullLogger } from "./logger/nullLogger"; export { defaultLogger } from "./logger/defaultLogger"; export { EnvUtils } from "./env"; +export { Yagna, YagnaApi } from "./yagna/yagna"; diff --git a/src/utils/yagna/gsb.ts b/src/utils/yagna/gsb.ts new file mode 100644 index 000000000..0335102fa --- /dev/null +++ b/src/utils/yagna/gsb.ts @@ -0,0 +1,40 @@ +// TODO: replace with a proper REST API client once ya-client and ya-ts-client are updated +import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios"; +import { BaseAPI } from "ya-ts-client/dist/ya-net/base"; + +export type ServiceModel = { + serviceId: string; +}; + +export type GftpFileInfo = { + id: string; + url: string; +}; + +interface GsbRequestorApi { + createService(fileInfo: GftpFileInfo, components: string[], options?: AxiosRequestConfig): AxiosPromise; + deleteService(id: string, options?: AxiosRequestConfig): AxiosPromise; +} + +export class RequestorApi extends BaseAPI implements GsbRequestorApi { + async createService(fileInfo: GftpFileInfo, components: string[]): Promise> { + return this.axios.post( + `${this.basePath}services`, + { + listen: { + on: `/public/gftp/${fileInfo.id}`, + components, + }, + }, + { + headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, + }, + ); + } + + async deleteService(id: string): Promise> { + return this.axios.delete(`${this.basePath}/services/${id}`, { + headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, + }); + } +} diff --git a/src/utils/yagna/identity.ts b/src/utils/yagna/identity.ts new file mode 100644 index 000000000..eaa1ea1b7 --- /dev/null +++ b/src/utils/yagna/identity.ts @@ -0,0 +1,21 @@ +// TODO: replace with a proper REST API client once ya-client and ya-ts-client are updated +// https://github.com/golemfactory/golem-js/issues/290 +import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "axios"; +import { BaseAPI } from "ya-ts-client/dist/ya-net/base"; +export interface IdentityModel { + identity: string; + name: string; + role: string; +} + +interface IdentityRequestorApi { + getIdentity(options?: AxiosRequestConfig): AxiosPromise; +} + +export class RequestorApi extends BaseAPI implements IdentityRequestorApi { + async getIdentity(): Promise> { + return this.axios.get(this.basePath + "/me", { + headers: { authorization: `Bearer ${this.configuration?.apiKey}` }, + }); + } +} diff --git a/src/utils/yagna/yagna.ts b/src/utils/yagna/yagna.ts new file mode 100644 index 000000000..53df3320c --- /dev/null +++ b/src/utils/yagna/yagna.ts @@ -0,0 +1,112 @@ +import { RequestorControlApi, RequestorStateApi } from "ya-ts-client/dist/ya-activity/api"; +import { RequestorApi as MarketRequestorApi } from "ya-ts-client/dist/ya-market/api"; +import { RequestorApi as NetworkRequestorApi } from "ya-ts-client/dist/ya-net/api"; +import { RequestorApi as PaymentRequestorApi } from "ya-ts-client/dist/ya-payment/api"; +import { RequestorApi as IdentityRequestorApi } from "./identity"; +import { RequestorApi as GsbRequestorApi } from "./gsb"; +import { Agent } from "http"; +import { Configuration } from "ya-ts-client/dist/ya-payment"; +import { EnvUtils } from "../env"; +import { AxiosError } from "axios"; + +export type YagnaApi = { + market: MarketRequestorApi; + activity: { control: RequestorControlApi; state: RequestorStateApi }; + net: NetworkRequestorApi; + payment: PaymentRequestorApi; + identity: IdentityRequestorApi; + gsb: GsbRequestorApi; + yagnaOptions: YagnaOptions; +}; + +export type YagnaOptions = { + apiKey?: string; + basePath?: string; +}; + +const CONNECTIONS_ERROR_CODES = ["ECONNREFUSED"]; + +export class Yagna { + private readonly httpAgent: Agent; + private readonly controller: AbortController; + protected readonly apiKey: string; + protected readonly apiBaseUrl: string; + private readonly api: YagnaApi; + constructor(options?: YagnaOptions) { + this.httpAgent = new Agent({ keepAlive: true }); + this.controller = new AbortController(); + this.apiKey = options?.apiKey || EnvUtils.getYagnaAppKey(); + if (!this.apiKey) throw new Error("Api key not defined"); + this.apiBaseUrl = options?.basePath || EnvUtils.getYagnaApiUrl(); + this.api = this.createApi(); + } + + getApi(): YagnaApi { + return this.api; + } + + async connect(): Promise { + await this.api.identity.getIdentity(); + } + + async end(): Promise { + this.controller.abort(); + this.httpAgent.destroy?.(); + } + + protected createApi(): YagnaApi { + const apiConfig = this.getApiConfig(); + const api = { + market: new MarketRequestorApi(apiConfig, this.getApiUrl("market")), + activity: { + control: new RequestorControlApi(apiConfig, this.getApiUrl("activity")), + state: new RequestorStateApi(apiConfig, this.getApiUrl("activity")), + }, + net: new NetworkRequestorApi(apiConfig, this.getApiUrl("net")), + payment: new PaymentRequestorApi(apiConfig, this.getApiUrl("payment")), + identity: new IdentityRequestorApi(apiConfig, this.getApiUrl()), + gsb: new GsbRequestorApi(apiConfig, this.getApiUrl("gsb")), + yagnaOptions: { + apiKey: this.apiKey, + basePath: this.apiBaseUrl, + }, + }; + this.addErrorHandler(api); + return api; + } + + protected getApiConfig(): Configuration { + return new Configuration({ + apiKey: this.apiKey, + accessToken: this.apiKey, + baseOptions: { + httpAgent: this.httpAgent, + signal: this.controller.signal, + }, + }); + } + + protected getApiUrl(apiName?: string): string { + return apiName ? `${this.apiBaseUrl}/${apiName}-api/v1` : this.apiBaseUrl; + } + + protected errorHandler(error: AxiosError): Promise { + if (CONNECTIONS_ERROR_CODES.includes(error.code || "")) { + return Promise.reject( + `No connection to Yagna. Make sure the service is running at the address ${this.apiBaseUrl}`, + ); + } + return Promise.reject(error); + } + + protected addErrorHandler(api: YagnaApi) { + // Ugly solution until Yagna binding is refactored or replaced, + // and it will be possible to pass interceptors as the config params + api.net["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + api.market["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + api.activity.control["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + api.activity.state["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + api.payment["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + api.identity["axios"].interceptors.response.use(undefined, this.errorHandler.bind(this)); + } +} diff --git a/tests/e2e/_setup.ts b/tests/e2e/_setup.ts index fe77ae28a..de9ff4749 100644 --- a/tests/e2e/_setup.ts +++ b/tests/e2e/_setup.ts @@ -14,5 +14,5 @@ export default async function setUpGoth() { globalThis.__GOTH = new Goth(gothConfig); // Start Goth, but don't wait for an eternity - return await Promise.race([globalThis.__GOTH.start(), timeoutPromise(60)]); + return await Promise.race([globalThis.__GOTH.start(), timeoutPromise(180)]); } diff --git a/tests/mock/activity.mock.ts b/tests/mock/activity.mock.ts index 51f1c243b..019948f61 100644 --- a/tests/mock/activity.mock.ts +++ b/tests/mock/activity.mock.ts @@ -2,6 +2,7 @@ import { Activity, ActivityConfig, ActivityStateEnum, Result, ResultState } from import { Events, nullLogger } from "../../src"; import { ExeScriptRequest } from "../../src/activity/activity"; import { Readable } from "stream"; +import { YagnaApi } from "../../src/utils"; export class ActivityMock extends Activity { private _currentState: ActivityStateEnum = ActivityStateEnum.Ready; @@ -17,8 +18,8 @@ export class ActivityMock extends Activity { }; } - constructor(id?: string, agreementId?: string, options?: ActivityConfig) { - super(id, agreementId, (options ?? { logger: nullLogger() }) as unknown as ActivityConfig); + constructor(id: string, agreementId: string, yagnaApi: YagnaApi, options?: ActivityConfig) { + super(id, agreementId, yagnaApi, (options ?? { logger: nullLogger() }) as unknown as ActivityConfig); } async execute(script: ExeScriptRequest, stream?: boolean, timeout?: number): Promise { diff --git a/tests/mock/index.ts b/tests/mock/index.ts index 868aa575c..d217f9e79 100644 --- a/tests/mock/index.ts +++ b/tests/mock/index.ts @@ -5,3 +5,4 @@ export { agreementPoolServiceMock } from "./services/agrrement_pool"; export { networkServiceMock } from "./services/network"; export { StorageProviderMock } from "./entities/storage_provider"; export { LoggerMock } from "./utils/logger"; +export { YagnaMock } from "./rest/yagna"; diff --git a/tests/mock/rest/gsb.ts b/tests/mock/rest/gsb.ts new file mode 100644 index 000000000..9db443e79 --- /dev/null +++ b/tests/mock/rest/gsb.ts @@ -0,0 +1,12 @@ +import { GftpFileInfo, RequestorApi, ServiceModel } from "../../../src/utils/yagna/gsb"; +import { AxiosResponse } from "axios"; + +export class GsbApiMock extends RequestorApi { + async createService(fileInfo: GftpFileInfo, components: string[]): Promise> { + return new Promise((res) => res({ data: { serviceId: "test_id" } } as AxiosResponse)); + } + + async deleteService(id: string): Promise> { + return new Promise((res) => res({ data: true } as AxiosResponse)); + } +} diff --git a/tests/mock/rest/identity.ts b/tests/mock/rest/identity.ts index 08ba3e768..6d987a06a 100644 --- a/tests/mock/rest/identity.ts +++ b/tests/mock/rest/identity.ts @@ -1,6 +1,9 @@ import { TEST_IDENTITY } from "../fixtures"; -export const IdentityMock = { - getIdentity: async (): Promise => { - return TEST_IDENTITY; - }, -}; +import { IdentityModel, RequestorApi } from "../../../src/utils/yagna/identity"; +import { AxiosResponse } from "axios"; + +export class IdentityApiMock extends RequestorApi { + async getIdentity(): Promise> { + return new Promise((res) => res({ data: { identity: TEST_IDENTITY } } as AxiosResponse)); + } +} diff --git a/tests/mock/rest/market.ts b/tests/mock/rest/market.ts index 9ded0af9d..7f237c6d3 100644 --- a/tests/mock/rest/market.ts +++ b/tests/mock/rest/market.ts @@ -15,9 +15,6 @@ export const setExpectedError = (error) => (global.expectedError = error); export class MarketApiMock extends RequestorApi { private exampleProposals = [...proposalsInitial, proposalsDraft]; - constructor() { - super(); - } // @ts-ignore async createAgreement( createAgreementRequest: AgreementProposal, diff --git a/tests/mock/rest/yagna.ts b/tests/mock/rest/yagna.ts new file mode 100644 index 000000000..661bdbe0f --- /dev/null +++ b/tests/mock/rest/yagna.ts @@ -0,0 +1,40 @@ +/* eslint @typescript-eslint/ban-ts-comment: 0 */ +import { Yagna, YagnaApi } from "../../../src/utils"; +import { RequestorControlApiMock, RequestorSateApiMock } from "./activity"; +import { MarketApiMock } from "./market"; +import { EventSourceMock } from "../utils/event_source"; +import { PaymentApiMock } from "./payment"; +import { NetworkApiMock } from "./network"; +import { IdentityApiMock } from "./identity"; +import { GsbApiMock } from "./gsb"; + +jest.mock("eventsource", () => EventSourceMock); + +export class YagnaMock extends Yagna { + constructor() { + super({ apiKey: "test_api_key" }); + } + protected createApi(): YagnaApi { + return { + // @ts-ignore + market: new MarketApiMock(), + activity: { + // @ts-ignore + control: new RequestorControlApiMock(), + // @ts-ignore + state: new RequestorSateApiMock(), + }, + // @ts-ignore + net: new NetworkApiMock(), + + // @ts-ignore + payment: new PaymentApiMock(), + identity: new IdentityApiMock(), + gsb: new GsbApiMock(), + yagnaOptions: { + apiKey: this.apiKey, + basePath: this.apiBaseUrl, + }, + }; + } +} diff --git a/tests/mock/services/agrrement_pool.ts b/tests/mock/services/agrrement_pool.ts index 4936a1710..6e9e30c02 100644 --- a/tests/mock/services/agrrement_pool.ts +++ b/tests/mock/services/agrrement_pool.ts @@ -1,17 +1,19 @@ /* eslint @typescript-eslint/ban-ts-comment: 0 */ import { Agreement, AgreementPoolService } from "../../../src/agreement"; import { agreementsApproved } from "../fixtures"; -import { AgreementConfig } from "../../../src/agreement/config"; +import { AgreementConfig } from "../../../src/agreement"; import { Proposal } from "../../../src/market"; +import { YagnaMock } from "../rest/yagna"; const proposals: Proposal[] = []; const invalidProviderIds: string[] = []; const provider = { id: "test_provider_id", name: "Test Provider" }; +const yagnaApi = new YagnaMock().getApi(); // @ts-ignore export const agreementPoolServiceMock: AgreementPoolService = { async getAgreement(): Promise { const agreementData = agreementsApproved[0]; - return new Agreement(agreementData.agreementId, provider, new AgreementConfig()); + return new Agreement(agreementData.agreementId, provider, yagnaApi, new AgreementConfig()); }, async addProposal(proposal: Proposal) { proposals.push(proposal); diff --git a/tests/unit/_setup.ts b/tests/unit/_setup.ts deleted file mode 100644 index 5ee03d3cf..000000000 --- a/tests/unit/_setup.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { RequestorControlApiMock, RequestorSateApiMock } from "../mock/rest/activity"; -import { MarketApiMock } from "../mock/rest/market"; -import { EventSourceMock } from "../mock/utils/event_source"; -import { PaymentApiMock } from "../mock/rest/payment"; -import { NetworkApiMock } from "../mock/rest/network"; -import { IdentityMock } from "../mock/rest/identity"; - -jest.mock("ya-ts-client/dist/ya-payment/api", () => ({ - RequestorApi: PaymentApiMock, -})); -jest.mock("ya-ts-client/dist/ya-net/api", () => ({ - RequestorApi: NetworkApiMock, -})); -jest.mock("ya-ts-client/dist/ya-market/api", () => ({ - RequestorApi: MarketApiMock, -})); -jest.mock("ya-ts-client/dist/ya-activity/api", () => ({ - RequestorControlApi: RequestorControlApiMock, - RequestorStateApi: RequestorSateApiMock, -})); -jest.mock("../../src/network/identity", () => IdentityMock); - -jest.mock("eventsource", () => EventSourceMock); - -process.env["YAGNA_APPKEY"] = "test_key"; diff --git a/tests/unit/activity.test.ts b/tests/unit/activity.test.ts index 5bd045011..3551b66c1 100644 --- a/tests/unit/activity.test.ts +++ b/tests/unit/activity.test.ts @@ -1,18 +1,19 @@ import * as activityMock from "../mock/rest/activity"; import { setExpectedErrorEvents, setExpectedEvents } from "../mock/utils/event_source"; -import { StorageProviderMock } from "../mock"; +import { StorageProviderMock, YagnaMock } from "../mock"; import { Activity, ActivityStateEnum } from "../../src/activity"; import { sleep } from "../../src/utils"; import { Deploy, Start, Run, Terminate, UploadFile, DownloadFile, Script, Capture } from "../../src/script"; describe("Activity", () => { + const yagnaApi = new YagnaMock().getApi(); beforeEach(() => { activityMock.clear(); }); describe("Creating", () => { it("should create activity", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); expect(activity).toBeInstanceOf(Activity); const GUID_REGEX = /^(?:\{{0,1}(?:[0-9a-fA-F]){8}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){12}\}{0,1})$/; @@ -22,14 +23,14 @@ describe("Activity", () => { describe("Executing", () => { it("should execute commands on activity", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const streamResult = await activity.execute(new Deploy().toExeScriptRequest()); const { value: result } = await streamResult[Symbol.asyncIterator]().next(); expect(result.result).toEqual("Ok"); }); it("should execute commands and get state", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const streamResult = await activity.execute(new Run("test_command").toExeScriptRequest()); const { value: result } = await streamResult[Symbol.asyncIterator]().next(); activityMock.setExpectedStates([ActivityStateEnum.Ready, null]); @@ -39,7 +40,7 @@ describe("Activity", () => { }); it("should execute script and get results by iterator", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new Run("test_command1"); @@ -65,7 +66,7 @@ describe("Activity", () => { }); it("should execute script and get results by events", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new UploadFile(new StorageProviderMock(), "testSrc", "testDst"); @@ -108,7 +109,7 @@ describe("Activity", () => { }); it("should execute script by streaming batch", async () => { - const activity = await Activity.create("test_id_2"); + const activity = await Activity.create("test_id_2", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const capture: Capture = { @@ -172,7 +173,7 @@ describe("Activity", () => { describe("Getting state", () => { it("should get activity state", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); activityMock.setExpectedStates([ActivityStateEnum.Ready, ActivityStateEnum.Terminated]); const state = await activity.getState(); expect(state).toEqual(ActivityStateEnum.Ready); @@ -181,7 +182,7 @@ describe("Activity", () => { describe("Cancelling", () => { it("should cancel activity", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new Run("test_command1"); @@ -202,7 +203,7 @@ describe("Activity", () => { }); it("should cancel activity while streaming batch", async () => { - const activity = await Activity.create("test_id_3"); + const activity = await Activity.create("test_id_3", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const capture: Capture = { @@ -227,7 +228,7 @@ describe("Activity", () => { describe("Error handling", () => { it("should handle some error", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new Run("test_command1"); @@ -250,7 +251,7 @@ describe("Activity", () => { }); it("should handle gsb error", async () => { - const activity = await Activity.create("test_id", { + const activity = await Activity.create("test_id", yagnaApi, { activityExeBatchResultsFetchInterval: 10, }); const command1 = new Deploy(); @@ -279,7 +280,7 @@ describe("Activity", () => { }); it("should handle termination error", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new Run("test_command1"); @@ -303,7 +304,7 @@ describe("Activity", () => { }); it("should handle timeout error", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const command3 = new Run("test_command1"); @@ -323,7 +324,7 @@ describe("Activity", () => { }); it("should handle timeout error while streaming batch", async () => { - const activity = await Activity.create("test_id_3", { activityExecuteTimeout: 1 }); + const activity = await Activity.create("test_id_3", yagnaApi, { activityExecuteTimeout: 1 }); const command1 = new Deploy(); const command2 = new Start(); const capture: Capture = { @@ -346,7 +347,7 @@ describe("Activity", () => { }); it("should handle some error while streaming batch", async () => { - const activity = await Activity.create("test_id_5"); + const activity = await Activity.create("test_id_5", yagnaApi); const command1 = new Deploy(); const command2 = new Start(); const capture: Capture = { @@ -379,7 +380,7 @@ describe("Activity", () => { describe("Destroying", () => { it("should stop activity", async () => { - const activity = await Activity.create("test_id"); + const activity = await Activity.create("test_id", yagnaApi); expect(await activity.stop()).toEqual(true); }); }); diff --git a/tests/unit/agreement.test.ts b/tests/unit/agreement.test.ts index 53b20568e..8aabbbf40 100644 --- a/tests/unit/agreement.test.ts +++ b/tests/unit/agreement.test.ts @@ -1,14 +1,15 @@ -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import { Agreement } from "../../src/agreement"; import { AgreementStateEnum } from "ya-ts-client/dist/ya-market/src/models/agreement"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); describe("Agreement", () => { beforeEach(() => logger.clear()); describe("create()", () => { it("should create agreement for given proposal Id", async () => { - const agreement = await Agreement.create("test_proposal_id", { logger }); + const agreement = await Agreement.create("test_proposal_id", yagnaApi, { logger }); expect(agreement).toBeInstanceOf(Agreement); expect(agreement.id).toHaveLength(64); expect(logger.logs).toMatch(/Agreement .* created/); @@ -17,7 +18,7 @@ describe("Agreement", () => { describe("provider", () => { it("should be a instance ProviderInfo with provider details", async () => { - const agreement = await Agreement.create("test_proposal_id", { logger }); + const agreement = await Agreement.create("test_proposal_id", yagnaApi, { logger }); expect(agreement).toBeInstanceOf(Agreement); expect(agreement.provider.id).toEqual(expect.any(String)); expect(agreement.provider.name).toEqual(expect.any(String)); @@ -26,14 +27,14 @@ describe("Agreement", () => { describe("getState()", () => { it("should return state of agreement", async () => { - const agreement = await Agreement.create("test_proposal_id", { logger }); + const agreement = await Agreement.create("test_proposal_id", yagnaApi, { logger }); expect(await agreement.getState()).toEqual(AgreementStateEnum.Approved); }); }); describe("terminate()", () => { it("should terminate agreement", async () => { - const agreement = await Agreement.create("test_proposal_id", { logger }); + const agreement = await Agreement.create("test_proposal_id", yagnaApi, { logger }); await agreement.terminate(); expect(logger.logs).toMatch(/Agreement .* terminated/); }); @@ -41,7 +42,7 @@ describe("Agreement", () => { describe("confirm()", () => { it("should confirm agreement", async () => { - const agreement = await Agreement.create("test_proposal_id", { logger }); + const agreement = await Agreement.create("test_proposal_id", yagnaApi, { logger }); await agreement.confirm(); expect(logger.logs).toMatch(/Agreement .* approved/); }); diff --git a/tests/unit/agreement_pool_service.test.ts b/tests/unit/agreement_pool_service.test.ts index f72c3a925..5bd28201f 100644 --- a/tests/unit/agreement_pool_service.test.ts +++ b/tests/unit/agreement_pool_service.test.ts @@ -1,11 +1,12 @@ -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import { Agreement, AgreementPoolService } from "../../src/agreement"; import { RequestorApi } from "ya-ts-client/dist/ya-market/api"; import { Proposal as ProposalModel } from "ya-ts-client/dist/ya-market/src/models/proposal"; import { DemandOfferBase } from "ya-ts-client/dist/ya-market"; -import { Proposal } from "../../src/market/proposal"; +import { Proposal } from "../../src/market"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); const createProposal = (id) => { return new Proposal( @@ -39,7 +40,7 @@ describe("Agreement Pool Service", () => { describe("run()", () => { it("should start service", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); expect(logger.logs).toContain("Agreement Pool Service has started"); await agreementService.end(); @@ -47,7 +48,7 @@ describe("Agreement Pool Service", () => { }); describe("end()", () => { it("should stop service", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.end(); expect(logger.logs).toContain("Agreement Pool Service has been stopped"); @@ -55,7 +56,7 @@ describe("Agreement Pool Service", () => { }); describe("getAvailableAgreement()", () => { it("should create and return agreement from available proposal pool", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); @@ -63,7 +64,7 @@ describe("Agreement Pool Service", () => { expect(agreement).toBeInstanceOf(Agreement); }); it("should return agreement if is available in the pool", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); const agreement1 = await agreementService.getAgreement(); @@ -74,7 +75,7 @@ describe("Agreement Pool Service", () => { }); describe("releaseAgreement()", () => { it("should return agreement to the pool if flag reuse if on", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); const agreement = await agreementService.getAgreement(); @@ -83,7 +84,7 @@ describe("Agreement Pool Service", () => { }); it("should terminate agreement if flag reuse if off", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); const agreement = await agreementService.getAgreement(); @@ -93,7 +94,7 @@ describe("Agreement Pool Service", () => { }); it("should warn if there is no agreement with given id", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); const agreement = await agreementService.getAgreement(); @@ -104,7 +105,7 @@ describe("Agreement Pool Service", () => { describe("addProposal()", () => { it("should add proposal to pool", async () => { - const agreementService = new AgreementPoolService({ logger }); + const agreementService = new AgreementPoolService(yagnaApi, { logger }); await agreementService.run(); await agreementService.addProposal(createProposal("proposal-id")); diff --git a/tests/unit/allocation.test.ts b/tests/unit/allocation.test.ts index 47e8577b9..a34fe092c 100644 --- a/tests/unit/allocation.test.ts +++ b/tests/unit/allocation.test.ts @@ -1,20 +1,21 @@ -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import { Allocation } from "../../src/payment"; const logger = new LoggerMock(); const account = { address: "test_address", platform: "test_platform" }; +const yagnaApi = new YagnaMock().getApi(); describe("Allocation", () => { beforeEach(() => logger.clear()); describe("Creating", () => { it("should create allocation", async () => { - const allocation = await Allocation.create({ account }); + const allocation = await Allocation.create(yagnaApi, { account }); expect(allocation).toBeInstanceOf(Allocation); }); it("should not create allocation with empty account parameters", async () => { - await expect(Allocation.create({ account: { address: "", platform: "" } })).rejects.toThrow( + await expect(Allocation.create(yagnaApi, { account: { address: "", platform: "" } })).rejects.toThrow( "Account address and payment platform are required", ); }); diff --git a/tests/unit/demand.test.ts b/tests/unit/demand.test.ts index b3934f622..42a89d52c 100644 --- a/tests/unit/demand.test.ts +++ b/tests/unit/demand.test.ts @@ -2,14 +2,16 @@ import { setExpectedProposals } from "../mock/rest/market"; import { Demand, Proposal, DemandEventType, DemandEvent } from "../../src/market"; import { allocationMock, packageMock, LoggerMock } from "../mock"; import { proposalsInitial } from "../mock/fixtures"; +import { YagnaMock } from "../mock/rest/yagna"; const subnetTag = "testnet"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); describe("Demand", () => { describe("Creating", () => { it("should create and publish demand", async () => { - const demand = await Demand.create(packageMock, allocationMock, { subnetTag, logger }); + const demand = await Demand.create(packageMock, allocationMock, yagnaApi, { subnetTag, logger }); expect(demand).toBeInstanceOf(Demand); expect(logger.logs).toContain("Demand published on the market"); await demand.unsubscribe(); @@ -17,7 +19,7 @@ describe("Demand", () => { }); describe("Processing", () => { it("should get proposal after publish demand", async () => { - const demand = await Demand.create(packageMock, allocationMock, { subnetTag }); + const demand = await Demand.create(packageMock, allocationMock, yagnaApi, { subnetTag }); setExpectedProposals(proposalsInitial); const event: DemandEvent = await new Promise((res) => demand.addEventListener(DemandEventType, (e) => res(e as DemandEvent)), diff --git a/tests/unit/jest.config.json b/tests/unit/jest.config.json index ac871ff97..008f1b45a 100644 --- a/tests/unit/jest.config.json +++ b/tests/unit/jest.config.json @@ -1,7 +1,6 @@ { "preset": "ts-jest", "testEnvironment": "node", - "setupFiles": ["/_setup.ts"], "roots": ["../../src", "./"], "testMatch": ["**/*.test.ts", "**/*.spec.ts"] } diff --git a/tests/unit/market_service.test.ts b/tests/unit/market_service.test.ts index cae7ff4ec..ba2db16a9 100644 --- a/tests/unit/market_service.test.ts +++ b/tests/unit/market_service.test.ts @@ -1,6 +1,6 @@ import { setExpectedProposals } from "../mock/rest/market"; import { MarketService, ProposalFilters } from "../../src/market"; -import { agreementPoolServiceMock, packageMock, LoggerMock, allocationMock } from "../mock"; +import { agreementPoolServiceMock, packageMock, LoggerMock, allocationMock, YagnaMock } from "../mock"; import { proposalsInitial, proposalsDraft, @@ -9,6 +9,7 @@ import { } from "../mock/fixtures"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); describe("Market Service", () => { beforeEach(() => { @@ -16,7 +17,7 @@ describe("Market Service", () => { }); it("should start service and publish demand", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); expect(logger.logs).toContain("Market Service has started"); expect(logger.logs).toContain("Demand published on the market"); @@ -25,7 +26,7 @@ describe("Market Service", () => { }); it("should respond initial proposal", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); setExpectedProposals(proposalsInitial); await logger.expectToInclude("Proposal has been responded", 10); @@ -33,7 +34,7 @@ describe("Market Service", () => { }); it("should add draft proposal to agreement pool", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); setExpectedProposals(proposalsDraft); await logger.expectToInclude("Proposal has been confirmed", 10); @@ -43,7 +44,7 @@ describe("Market Service", () => { }); it("should reject initial proposal without common payment platform", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); setExpectedProposals([proposalsInitial[6]]); await logger.expectToMatch(/Proposal has been rejected .* Reason: No common payment platform/, 10); @@ -51,14 +52,14 @@ describe("Market Service", () => { }); it("should reject when no common payment platform", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); setExpectedProposals(proposalsWrongPaymentPlatform); await logger.expectToMatch(/No common payment platform/, 10); await marketService.end(); }); it("should reject initial proposal when debit note acceptance timeout too short", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { logger }); + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger }); await marketService.run(packageMock, allocationMock); setExpectedProposals(proposalsShortDebitNoteTimeout); await logger.expectToMatch(/Debit note acceptance timeout too short/, 10); @@ -66,7 +67,7 @@ describe("Market Service", () => { }); it("should reject when proposal rejected by Proposal Filter", async () => { const proposalAlwaysBanFilter = () => Promise.resolve(false); - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: proposalAlwaysBanFilter, }); @@ -76,7 +77,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should reject when proposal rejected by BlackListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.blackListProposalIdsFilter(["0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655"]), }); @@ -86,7 +87,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should reject when proposal rejected by BlackListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.blackListProposalRegexpFilter(/golem2004/), }); @@ -96,7 +97,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should reject when proposal rejected by WhiteListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.whiteListProposalIdsFilter(["0x123455"]), }); @@ -106,7 +107,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should reject when proposal rejected by WhiteListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.whiteListProposalRegexpFilter(/abcdefg/), }); @@ -116,7 +117,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should respond when provider id is whitelisted by WhiteListIds Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.whiteListProposalIdsFilter(["0xee8993fe1dcff6b131d3fd759c6b3ddcb82d1655"]), }); @@ -126,7 +127,7 @@ describe("Market Service", () => { await marketService.end(); }); it("should respond when provider name is whitelisted by WhiteListNames Proposal Filter", async () => { - const marketService = new MarketService(agreementPoolServiceMock, { + const marketService = new MarketService(agreementPoolServiceMock, yagnaApi, { logger, proposalFilter: ProposalFilters.whiteListProposalRegexpFilter(/golem2004/), }); diff --git a/tests/unit/network.test.ts b/tests/unit/network.test.ts index e5df5004e..ace2f38c5 100644 --- a/tests/unit/network.test.ts +++ b/tests/unit/network.test.ts @@ -1,9 +1,11 @@ import { Network } from "../../src/network"; +import { YagnaMock } from "../mock"; +const yagnaApi = new YagnaMock().getApi(); describe("Network", () => { describe("Creating", () => { it("should create network", async () => { - const network = await Network.create({ networkOwnerId: "test_owner_id" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "test_owner_id" }); const { ip, mask, nodes } = network.getNetworkInfo(); expect(nodes["192.168.0.1"]).toEqual("test_owner_id"); expect(Object.keys(nodes).length).toEqual(1); @@ -12,35 +14,35 @@ describe("Network", () => { }); it("should create network with 16 bit mask", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.7.0/16" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/16" }); const { ip, mask } = network.getNetworkInfo(); expect({ ip, mask }).toEqual({ ip: "192.168.0.0", mask: "255.255.0.0" }); }); it("should create network with 24 bit mask", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.7.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/24" }); const { ip, mask } = network.getNetworkInfo(); expect({ ip, mask }).toEqual({ ip: "192.168.7.0", mask: "255.255.255.0" }); }); it("should create network with 8 bit mask", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.7.0/8" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.7.0/8" }); const { ip, mask } = network.getNetworkInfo(); expect({ ip, mask }).toEqual({ ip: "192.0.0.0", mask: "255.0.0.0" }); }); it("should not create network with invalid ip", async () => { - const shouldFail = Network.create({ networkOwnerId: "1", networkIp: "123.1.2" }); + const shouldFail = Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "123.1.2" }); await expect(shouldFail).rejects.toThrow("Cidr notation should be in the form [ip number]/[range]"); }); it("should not create network without mask", async () => { - const shouldFail = Network.create({ networkOwnerId: "1", networkIp: "1.1.1.1" }); + const shouldFail = Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "1.1.1.1" }); await expect(shouldFail).rejects.toThrow("Cidr notation should be in the form [ip number]/[range]"); }); it("should create network with custom options", async () => { - const network = await Network.create({ + const network = await Network.create(yagnaApi, { networkIp: "192.168.0.1", networkOwnerId: "owner_1", networkOwnerIp: "192.168.0.7", @@ -55,13 +57,13 @@ describe("Network", () => { describe("Nodes", () => { it("should add node", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); const { id, ip } = await network.addNode("7", "192.168.0.7"); expect({ id, ip: ip.toString() }).toEqual({ id: "7", ip: "192.168.0.7" }); }); it("should add a few nodes", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); const node2 = await network.addNode("2", "192.168.0.3"); const node3 = await network.addNode("3"); const node4 = await network.addNode("4"); @@ -71,12 +73,12 @@ describe("Network", () => { }); it("should not add node with an existing ID", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); await expect(network.addNode("1")).rejects.toThrow("ID '1' has already been assigned in this network"); }); it("should not add node with an existing IP", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); await network.addNode("2", "192.168.0.3"); await expect(network.addNode("3", "192.168.0.3")).rejects.toThrow( "IP '192.168.0.3' has already been assigned in this network", @@ -84,21 +86,21 @@ describe("Network", () => { }); it("should not add node with address outside the network range", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); await expect(network.addNode("2", "192.168.2.2")).rejects.toThrow( "The given IP ('192.168.2.2') address must belong to the network ('192.168.0.0/24')", ); }); it("should not add too many nodes", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/30" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/30" }); await network.addNode("2"); await network.addNode("3"); await expect(network.addNode("4")).rejects.toThrow("No more addresses available in 192.168.0.0/30"); }); it("should get node network config", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); const node = await network.addNode("2"); expect(node.getNetworkConfig()).toEqual({ net: [ @@ -117,10 +119,10 @@ describe("Network", () => { }); it("should get node websocket uri", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); const node = await network.addNode("2"); expect(node.getWebsocketUri(22)).toEqual( - `ws://${process.env?.YAGNA_API_URL?.substring(7) || "127.0.0.1:7465"}/net-api/v1/net/${ + `ws://${process.env?.YAGNA_API_URL?.substring(7) || "localhost"}/net-api/v1/net/${ network.id }/tcp/192.168.0.2/22`, ); @@ -129,13 +131,13 @@ describe("Network", () => { describe("Removing", () => { it("should remove network", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); expect(await network.remove()).toEqual(true); }); it("should not remove network that doesn't exist", async () => { - const network = await Network.create({ networkOwnerId: "1", networkIp: "192.168.0.0/24" }); - network["config"]["api"]["setExpectedError"]({ status: 404 }); + const network = await Network.create(yagnaApi, { networkOwnerId: "1", networkIp: "192.168.0.0/24" }); + network["yagnaApi"]["net"]["setExpectedError"]({ status: 404 }); expect(await network.remove()).toEqual(false); }); }); diff --git a/tests/unit/network_service.test.ts b/tests/unit/network_service.test.ts index 91d63764e..08ebe4cd1 100644 --- a/tests/unit/network_service.test.ts +++ b/tests/unit/network_service.test.ts @@ -1,7 +1,7 @@ -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import { NetworkService } from "../../src/network"; const logger = new LoggerMock(); - +const yagnaApi = new YagnaMock().getApi(); describe("Network Service", () => { beforeEach(() => { logger.clear(); @@ -9,7 +9,7 @@ describe("Network Service", () => { describe("Creating", () => { it("should start service and create network", async () => { - const networkService = new NetworkService({ logger }); + const networkService = new NetworkService(yagnaApi, { logger }); await networkService.run("test_owner_id"); await logger.expectToMatch(/Network created: ID: .*, IP: 192.168.0.0, Mask: 255.255.255.0/, 10); await logger.expectToInclude("Network Service has started"); @@ -19,7 +19,7 @@ describe("Network Service", () => { describe("Nodes", () => { it("should add node to network", async () => { - const networkService = new NetworkService({ logger }); + const networkService = new NetworkService(yagnaApi, { logger }); await networkService.run("test_owner_id"); await networkService.addNode("provider_2"); await logger.expectToInclude("Node has added to the network. ID: provider_2, IP: 192.168.0.2", 10); @@ -27,7 +27,7 @@ describe("Network Service", () => { }); it("should not add node if the service is not started", async () => { - const networkService = new NetworkService({ logger }); + const networkService = new NetworkService(yagnaApi, { logger }); const result = networkService.addNode("provider_2"); await expect(result).rejects.toThrow("The service is not started and the network does not exist"); }); @@ -35,7 +35,7 @@ describe("Network Service", () => { describe("Removing", () => { it("should end service and remove network", async () => { - const networkService = new NetworkService({ logger }); + const networkService = new NetworkService(yagnaApi, { logger }); await networkService.run("test_owner_id"); await networkService.end(); await logger.expectToInclude("Network has removed: ID", 60); diff --git a/tests/unit/package.test.ts b/tests/unit/package.test.ts index f912711f9..d7c245d69 100644 --- a/tests/unit/package.test.ts +++ b/tests/unit/package.test.ts @@ -1,5 +1,5 @@ import { LoggerMock } from "../mock"; -import { Package } from "../../src/package"; +import { Package } from "../../src"; const logger = new LoggerMock(); describe("Package", () => { diff --git a/tests/unit/payment_service.test.ts b/tests/unit/payment_service.test.ts index 46603bc03..d520a9083 100644 --- a/tests/unit/payment_service.test.ts +++ b/tests/unit/payment_service.test.ts @@ -1,10 +1,11 @@ import { setExpectedDebitNotes, setExpectedEvents, setExpectedInvoices, clear } from "../mock/rest/payment"; -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import { PaymentService, Allocation, PaymentFilters } from "../../src/payment"; import { agreement } from "../mock/entities/agreement"; import { debitNotesEvents, debitNotes, invoices, invoiceEvents } from "../mock/fixtures"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); describe("Payment Service", () => { beforeEach(() => { @@ -14,14 +15,14 @@ describe("Payment Service", () => { describe("Allocations", () => { it("should create allocation", async () => { - const paymentService = new PaymentService(); + const paymentService = new PaymentService(yagnaApi); const allocation = await paymentService.createAllocation(); expect(allocation).toBeInstanceOf(Allocation); await paymentService.end(); }); it("should release created allocation when service stopped", async () => { - const paymentService = new PaymentService({ logger }); + const paymentService = new PaymentService(yagnaApi, { logger }); const allocation = await paymentService.createAllocation(); const releaseSpy = jest.spyOn(allocation, "release"); await paymentService.end(); @@ -31,7 +32,7 @@ describe("Payment Service", () => { describe("Processing payments", () => { it("should accept and process invoice for agreement", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, paymentTimeout: 100, }); @@ -46,7 +47,7 @@ describe("Payment Service", () => { }); it("should accept and process debit note for agreement", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, }); setExpectedEvents(debitNotesEvents); @@ -60,7 +61,7 @@ describe("Payment Service", () => { it("should reject when debit note rejected by DebitNote Filter", async () => { const alwaysRejectDebitNoteFilter = async () => false; - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, debitNotesFilter: alwaysRejectDebitNoteFilter, }); @@ -78,7 +79,7 @@ describe("Payment Service", () => { it("should reject when invoice rejected by Invoice Filter", async () => { const alwaysRejectInvoiceFilter = async () => false; - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, invoiceFilter: alwaysRejectInvoiceFilter, }); @@ -96,7 +97,7 @@ describe("Payment Service", () => { }); it("should reject when debit note rejected by DebitNoteMaxAmount Filter", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, debitNotesFilter: PaymentFilters.acceptMaxAmountDebitNoteFilter(0.00001), }); @@ -113,7 +114,7 @@ describe("Payment Service", () => { }); it("should reject when invoice rejected by MaxAmountInvoice Filter", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(0.00001), }); @@ -131,7 +132,7 @@ describe("Payment Service", () => { }); it("should accept when debit note filtered by DebitNoteMaxAmount Filter", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, debitNotesFilter: PaymentFilters.acceptMaxAmountDebitNoteFilter(7), }); @@ -145,7 +146,7 @@ describe("Payment Service", () => { }); it("should accept when invoice filtered by MaxAmountInvoice Filter", async () => { - const paymentService = new PaymentService({ + const paymentService = new PaymentService(yagnaApi, { logger, invoiceFilter: PaymentFilters.acceptMaxAmountInvoiceFilter(7), }); diff --git a/tests/unit/stats_service.test.ts b/tests/unit/stats_service.test.ts index 4741c57e4..6101f3d21 100644 --- a/tests/unit/stats_service.test.ts +++ b/tests/unit/stats_service.test.ts @@ -2,7 +2,7 @@ import { Events } from "../../src/events"; import { LoggerMock } from "../mock"; import { StatsService } from "../../src/stats/service"; import { setMaxListeners } from "events"; -import { ProposalDetails } from "../../src/market/proposal"; +import { ProposalDetails } from "../../src/market"; const logger = new LoggerMock(); const eventTarget = new EventTarget(); const statServiceOptions = { logger, eventTarget }; diff --git a/tests/unit/tasks_service.test.ts b/tests/unit/tasks_service.test.ts index b6ce0def1..2e343d569 100644 --- a/tests/unit/tasks_service.test.ts +++ b/tests/unit/tasks_service.test.ts @@ -1,7 +1,7 @@ import * as activityMock from "../mock/rest/activity"; import { Task, TaskQueue, TaskService, Worker } from "../../src/task"; -import { agreementPoolServiceMock, paymentServiceMock, networkServiceMock, LoggerMock } from "../mock"; -import { Result } from "../../src/activity"; +import { agreementPoolServiceMock, paymentServiceMock, networkServiceMock, LoggerMock, YagnaMock } from "../mock"; +import { Result } from "../../src"; let queue; const logger = new LoggerMock(); @@ -16,11 +16,18 @@ describe("Task Service", () => { const task = new Task("1", worker); queue.addToEnd(task); activityMock.setExpectedExeResults([{ stdout: "some_shell_results" }]); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - }); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + taskRunningInterval: 10, + activityStateCheckingInterval: 10, + }, + ); service.run().catch((e) => console.error(e)); await logger.expectToMatch(/Activity .* created/, 500); expect(task.isFinished()).toEqual(true); @@ -38,10 +45,17 @@ describe("Task Service", () => { queue.addToEnd(task1); queue.addToEnd(task2); queue.addToEnd(task3); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - maxParallelTasks: 2, - }); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + maxParallelTasks: 2, + }, + ); service.run().catch((e) => console.error(e)); expect(task1.isPending()).toEqual(true); expect(task2.isPending()).toEqual(true); @@ -54,16 +68,19 @@ describe("Task Service", () => { const worker: Worker = async (ctx) => ctx.run("some_shell_command"); const task = new Task("1", worker); queue.addToEnd(task); - activityMock.setExpectedExeResults([ - { result: "Ok" }, // deploy command - { result: "Ok" }, // start command - { stderr: "some_error", result: "Error" }, // run command - ]); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - taskRunningInterval: 100, - activityStateCheckingInterval: 100, - }); + activityMock.setExpectedErrors([new Error(), new Error(), new Error(), new Error(), new Error()]); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + taskRunningInterval: 100, + activityStateCheckingInterval: 100, + }, + ); service.run().catch((e) => console.error(e)); await logger.expectToInclude("Task 1 execution failed. Trying to redo the task. Attempt #", 700); await service.end(); @@ -77,11 +94,18 @@ describe("Task Service", () => { const task = new Task("1", worker, undefined, undefined, 2); queue.addToEnd(task); activityMock.setExpectedExeResults([{ result: "Ok", stdout: "invalid_value" }]); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - }); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + taskRunningInterval: 10, + activityStateCheckingInterval: 10, + }, + ); service.run().catch((e) => console.error(e)); await logger.expectToInclude( "Error: Task 1 has been rejected! Work rejected. Reason: Invalid value computed by provider", @@ -93,14 +117,21 @@ describe("Task Service", () => { it("should reject task if it failed max attempts", async () => { const worker: Worker = async (ctx) => ctx.run("some_shell_command"); - const task = new Task("1", worker); + const task = new Task("1", worker, undefined, undefined, 1); queue.addToEnd(task); - activityMock.setExpectedExeResults([{ stderr: "some_error", result: "Error" }]); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - }); + activityMock.setExpectedErrors(new Array(20).fill(new Error())); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + taskRunningInterval: 10, + activityStateCheckingInterval: 10, + }, + ); service.run().catch((e) => console.error(e)); await logger.expectToInclude("Error: Task 1 has been rejected!", 1800); expect(task.isRejected()).toEqual(true); @@ -116,16 +147,23 @@ describe("Task Service", () => { queue.addToEnd(task1); queue.addToEnd(task2); queue.addToEnd(task3); - const service = new TaskService(queue, agreementPoolServiceMock, paymentServiceMock, networkServiceMock, { - logger, - taskRunningInterval: 10, - activityStateCheckingInterval: 10, - maxParallelTasks: 2, - }); + const service = new TaskService( + new YagnaMock().getApi(), + queue, + agreementPoolServiceMock, + paymentServiceMock, + networkServiceMock, + { + logger, + taskRunningInterval: 10, + activityStateCheckingInterval: 10, + maxParallelTasks: 2, + }, + ); service.run().catch((e) => console.error(e)); - await logger.expectToMatch(/Init worker done in activity.*\nInit worker done in activity/, 700); + await logger.expectToMatch(/Init worker done in activity((.|\n)*)Init worker done in activity/, 700); await logger.expectToNotMatch( - /Init worker done in activity.*\nInit worker done in activity.*\nInit worker done in activity/, + /Init worker done in activity.*\nInit worker done in activity((.|\n)*)Init worker done in activity/, ); await new Promise((res) => setTimeout(res, 1000)); expect(task1.isFinished()).toEqual(true); diff --git a/tests/unit/work.test.ts b/tests/unit/work.test.ts index e7687c44a..97a6cf0f8 100644 --- a/tests/unit/work.test.ts +++ b/tests/unit/work.test.ts @@ -1,9 +1,10 @@ import * as activityMock from "../mock/rest/activity"; import { WorkContext, Worker } from "../../src/task"; -import { LoggerMock, StorageProviderMock } from "../mock"; +import { LoggerMock, StorageProviderMock, YagnaMock } from "../mock"; import { Activity, Result } from "../../src/activity"; import { Readable } from "stream"; const logger = new LoggerMock(); +const yagnaApi = new YagnaMock().getApi(); const storageProviderMock = new StorageProviderMock({ logger }); const isRunning = () => true; @@ -15,22 +16,21 @@ describe("Work Context", () => { describe("Executing", () => { it("should execute run command", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.run("some_shell_command"); - const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, isRunning }); + const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10 }); await ctx.before(); const results = await worker(ctx); expect(results?.stdout).toEqual("test_result"); }); it("should execute upload file command", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.uploadFile("./file.txt", "/golem/file.txt"); const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); await ctx.before(); const results = await worker(ctx); @@ -39,13 +39,12 @@ describe("Work Context", () => { }); it("should execute upload json command", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.uploadJson({ test: true }, "/golem/file.txt"); const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); await ctx.before(); const results = await worker(ctx); @@ -54,13 +53,12 @@ describe("Work Context", () => { }); it("should execute download file command", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.downloadFile("/golem/file.txt", "./file.txt"); const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); await ctx.before(); const results = await worker(ctx); @@ -70,7 +68,7 @@ describe("Work Context", () => { }); describe("Batch", () => { it("should execute batch as promise", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => { return ctx .beginBatch() @@ -84,7 +82,6 @@ describe("Work Context", () => { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); const expectedStdout = [ { stdout: "ok_run" }, @@ -101,7 +98,7 @@ describe("Work Context", () => { }); it("should execute batch as stream", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => { return ctx .beginBatch() @@ -115,7 +112,6 @@ describe("Work Context", () => { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); const expectedStdout = [ { stdout: "ok_run" }, @@ -142,13 +138,12 @@ describe("Work Context", () => { }); describe("Error handling", () => { it("should return a result with error in case the command to execute is invalid", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.beginBatch().run("invalid_shell_command").end(); const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); const expectedStdout = [{ result: "Error", stderr: "error", message: "Some error occurred" }]; activityMock.setExpectedExeResults(expectedStdout); @@ -160,13 +155,12 @@ describe("Work Context", () => { }); it("should catch error while executing batch as stream with invalid command", async () => { - const activity = await Activity.create("test_agreement_id"); + const activity = await Activity.create("test_agreement_id", yagnaApi); const worker: Worker = async (ctx) => ctx.beginBatch().run("invalid_shell_command").endStream(); const ctx = new WorkContext(activity, { logger, activityStateCheckingInterval: 10, storageProvider: storageProviderMock, - isRunning, }); const expectedStdout = [{ result: "Error", stderr: "error", message: "Some error occurred" }]; activityMock.setExpectedExeResults(expectedStdout); diff --git a/tests/unit/ws-browser.spec.ts b/tests/unit/ws-browser.spec.ts index bc39acfc8..f68e98bc8 100644 --- a/tests/unit/ws-browser.spec.ts +++ b/tests/unit/ws-browser.spec.ts @@ -1,8 +1,9 @@ import { consoleLogger, WebSocketBrowserStorageProvider, WebSocketStorageProviderOptions } from "../../src"; import { encode, toObject } from "flatbuffers/js/flexbuffers"; -import { LoggerMock } from "../mock"; +import { LoggerMock, YagnaMock } from "../mock"; import * as jsSha3 from "js-sha3"; import { TEST_IDENTITY } from "../mock/fixtures"; +import { AxiosResponse } from "axios"; jest.mock("uuid", () => ({ v4: () => "uuid" })); @@ -10,47 +11,29 @@ type UploadChunkChunk = { offset: number; content: Uint8Array }; describe("WebSocketBrowserStorageProvider", () => { let logger: LoggerMock; - const opts: WebSocketStorageProviderOptions = { - yagnaOptions: { - apiKey: "ApiKey", - basePath: "http://yagna", - }, - }; + const yagnaApi = new YagnaMock().getApi(); const createProvider = () => - new WebSocketBrowserStorageProvider({ - ...opts, + new WebSocketBrowserStorageProvider(yagnaApi, { logger, }); let provider: WebSocketBrowserStorageProvider; - const originalFetch = global.fetch; - const mockFetch = jest.fn(); - beforeEach(() => { logger = new LoggerMock(); provider = createProvider(); - jest.clearAllMocks(); }); - beforeAll(() => { - global.fetch = mockFetch; - }); - - afterAll(() => { - global.fetch = originalFetch; - }); - describe("constructor", () => { it("should create default logger", () => { - const provider = new WebSocketBrowserStorageProvider({ ...opts }); + const provider = new WebSocketBrowserStorageProvider(yagnaApi, {}); expect(provider["logger"]).toBeDefined(); }); it("should use provided logger", () => { const logger = consoleLogger(); - const provider = new WebSocketBrowserStorageProvider({ ...opts, logger }); + const provider = new WebSocketBrowserStorageProvider(yagnaApi, { logger }); expect(provider["logger"]).toBe(logger); }); }); @@ -272,69 +255,70 @@ describe("WebSocketBrowserStorageProvider", () => { describe("createService()", () => { it("should create service and return service info", async () => { const data = { servicesId: "ID" }; - mockFetch.mockImplementation((url, init: RequestInit) => { - expect(url.toString()).toEqual(`${opts.yagnaOptions.basePath}/gsb-api/v1/services`); - expect(init.headers!["Authorization"]).toBe(`Bearer ${opts.yagnaOptions.apiKey}`); + jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { return Promise.resolve({ status: 201, - json: () => Promise.resolve(data), - }); + data: { serviceId: "ID" }, + } as AxiosResponse); }); const result = await provider["createService"]({ id: "foo", url: "" }, []); - expect(mockFetch).toHaveBeenCalled(); + expect(yagnaApi.gsb.createService).toHaveBeenCalled(); expect(result.serviceId).toEqual("ID"); expect(result.url.toString()).toEqual( - `ws://yagna/gsb-api/v1/services/${data.servicesId}?authToken=${opts.yagnaOptions.apiKey}`, + `ws://127.0.0.1:7465/gsb-api/v1/services/${data.servicesId}?authToken=${yagnaApi.yagnaOptions.apiKey}`, ); }); it("should record the service for later release", async () => { const data = { servicesId: "ID" }; - mockFetch.mockResolvedValue({ - status: 201, - json: () => Promise.resolve(data), + jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { + return Promise.resolve({ + status: 201, + data: { serviceId: "ID" }, + } as AxiosResponse); }); - await provider["createService"]({ id: "foo", url: "/file" }, []); - expect(mockFetch).toHaveBeenCalled(); + expect(yagnaApi.gsb.createService).toHaveBeenCalled(); expect(provider["services"].size).toBe(1); expect(provider["services"].get("/file")).toEqual(data.servicesId); }); it("should throw when service creation fails", async () => { - mockFetch.mockResolvedValue({ - status: 404, + jest.spyOn(yagnaApi.gsb, "createService").mockImplementation((fileInfo, components) => { + return Promise.resolve({ + status: 404, + } as AxiosResponse); }); - await expect(() => { return provider["createService"]({ id: "foo", url: "/file" }, []); }).rejects.toThrow(); - expect(mockFetch).toHaveBeenCalled(); + expect(yagnaApi.gsb.createService).toHaveBeenCalled(); }); }); describe("deleteService()", () => { it("should call delete service API", async () => { - mockFetch.mockImplementation((url, init: RequestInit) => { - expect(url.toString()).toEqual(`${opts.yagnaOptions.basePath}/gsb-api/v1/services/Foo`); - expect(init.headers!["Authorization"]).toBe(`Bearer ${opts.yagnaOptions.apiKey}`); - return Promise.resolve({ status: 200 }); + jest.spyOn(yagnaApi.gsb, "deleteService").mockImplementation((id) => { + return Promise.resolve({ + status: 200, + } as AxiosResponse); }); - await provider["deleteService"]("Foo"); - expect(mockFetch).toHaveBeenCalled(); + expect(yagnaApi.gsb.deleteService).toHaveBeenCalled(); }); it("should throw when delete API fails", async () => { - mockFetch.mockResolvedValue({ - status: 404, + jest.spyOn(yagnaApi.gsb, "deleteService").mockImplementation((id) => { + return Promise.resolve({ + status: 404, + } as AxiosResponse); }); await expect(() => { return provider["deleteService"]("Foo"); }).rejects.toThrow(); - expect(mockFetch).toHaveBeenCalled(); + expect(yagnaApi.gsb.deleteService).toHaveBeenCalled(); }); });