Przeglądaj źródła

Integration tests: basic scenarios with CLIs (storage-node, distributor-node, @joystream/cli) - initializing buckets, creating a channel, requesting objects from distributor node

Leszek Wiesner 3 lat temu
rodzic
commit
1a6ad5e18b
42 zmienionych plików z 2465 dodań i 132 usunięć
  1. 6 1
      cli/src/base/UploadCommandBase.ts
  2. 0 3
      query-node/start-img.sh
  3. 0 3
      query-node/start.sh
  4. 7 0
      tests/network-tests/openapitools.json
  5. 11 3
      tests/network-tests/package.json
  6. 0 50
      tests/network-tests/run-storage-node-tests.sh
  7. 10 2
      tests/network-tests/run-tests.sh
  8. 33 16
      tests/network-tests/src/Api.ts
  9. 27 0
      tests/network-tests/src/apis/distributorNode/.openapi-generator-ignore
  10. 9 0
      tests/network-tests/src/apis/distributorNode/.openapi-generator/FILES
  11. 1 0
      tests/network-tests/src/apis/distributorNode/.openapi-generator/VERSION
  12. 394 0
      tests/network-tests/src/apis/distributorNode/api.ts
  13. 71 0
      tests/network-tests/src/apis/distributorNode/base.ts
  14. 138 0
      tests/network-tests/src/apis/distributorNode/common.ts
  15. 101 0
      tests/network-tests/src/apis/distributorNode/configuration.ts
  16. 18 0
      tests/network-tests/src/apis/distributorNode/index.ts
  17. 27 0
      tests/network-tests/src/apis/storageNode/.openapi-generator-ignore
  18. 5 0
      tests/network-tests/src/apis/storageNode/.openapi-generator/FILES
  19. 1 0
      tests/network-tests/src/apis/storageNode/.openapi-generator/VERSION
  20. 772 0
      tests/network-tests/src/apis/storageNode/api.ts
  21. 71 0
      tests/network-tests/src/apis/storageNode/base.ts
  22. 138 0
      tests/network-tests/src/apis/storageNode/common.ts
  23. 101 0
      tests/network-tests/src/apis/storageNode/configuration.ts
  24. 18 0
      tests/network-tests/src/apis/storageNode/index.ts
  25. 33 0
      tests/network-tests/src/cli/base.ts
  26. 38 0
      tests/network-tests/src/cli/distributor.ts
  27. 23 0
      tests/network-tests/src/cli/joystream.ts
  28. 55 0
      tests/network-tests/src/cli/storage.ts
  29. 111 0
      tests/network-tests/src/cli/utils.ts
  30. 4 1
      tests/network-tests/src/flows/proposals/manageLeaderRole.ts
  31. 4 1
      tests/network-tests/src/flows/proposals/workingGroupMintCapacityProposal.ts
  32. 0 45
      tests/network-tests/src/flows/storageNode/getContentFromStorageNode.ts
  33. 92 0
      tests/network-tests/src/flows/storagev2cli/createChannel.ts
  34. 54 0
      tests/network-tests/src/flows/storagev2cli/initDistributionBucket.ts
  35. 44 0
      tests/network-tests/src/flows/storagev2cli/initStorageBucket.ts
  36. 4 1
      tests/network-tests/src/flows/workingGroup/leaderSetup.ts
  37. 4 1
      tests/network-tests/src/flows/workingGroup/manageWorkerAsLead.ts
  38. 4 1
      tests/network-tests/src/flows/workingGroup/manageWorkerAsWorker.ts
  39. 4 1
      tests/network-tests/src/flows/workingGroup/workerPayout.ts
  40. 19 2
      tests/network-tests/src/scenarios/full.ts
  41. 1 1
      tests/network-tests/src/sender.ts
  42. 12 0
      yarn.lock

+ 6 - 1
cli/src/base/UploadCommandBase.ts

@@ -38,6 +38,7 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB
   private fileSizeCache: Map<string, number> = new Map<string, number>()
   private maxFileSize: undefined | BN = undefined
   private progressBarOptions: Options = {
+    noTTYOutput: true,
     format: `{barTitle} | {bar} | {value}/{total} KB processed`,
   }
 
@@ -63,9 +64,13 @@ export default abstract class UploadCommandBase extends ContentDirectoryCommandB
     let processedKB = 0
     const fileSizeKB = Math.ceil(fileSize / 1024)
     const progress = multiBar
-      ? multiBar.create(fileSizeKB, processedKB, { barTitle })
+      ? (multiBar.create(fileSizeKB, processedKB, { barTitle }) as SingleBar | undefined)
       : new SingleBar(this.progressBarOptions)
 
+    if (!progress) {
+      throw new Error('Provided multibar does not support noTTY mode!')
+    }
+
     progress.start(fileSizeKB, processedKB, { barTitle })
     return {
       fileStream: fs

+ 0 - 3
query-node/start-img.sh

@@ -8,9 +8,6 @@ set -a
 . ../.env
 set +a
 
-# Start the joystream-node first to allow fetching Olympia metadata during build (typegen)
-docker-compose up -d joystream-node
-
 # Bring up db
 docker-compose up -d db
 

+ 0 - 3
query-node/start.sh

@@ -8,9 +8,6 @@ set -a
 . ../.env
 set +a
 
-# Start the joystream-node first to allow fetching Olympia metadata during build (typegen)
-docker-compose up -d joystream-node
-
 # Only run codegen if no generated files found
 [ ! -d "generated/" ] && yarn build
 

+ 7 - 0
tests/network-tests/openapitools.json

@@ -0,0 +1,7 @@
+{
+  "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
+  "spaces": 2,
+  "generator-cli": {
+    "version": "5.2.1"
+  }
+}

+ 11 - 3
tests/network-tests/package.json

@@ -9,7 +9,10 @@
     "node-ts-strict": "node -r ts-node/register --unhandled-rejections=strict",
     "lint": "eslint . --quiet --ext .ts",
     "checks": "tsc --noEmit --pretty && prettier ./ --check && yarn lint",
-    "format": "prettier ./ --write "
+    "format": "prettier ./ --write ",
+    "generate:api:storage-node": "yarn openapi-generator-cli generate -i ../../storage-node-v2/src/api-spec/openapi.yaml -g typescript-axios -o ./src/apis/storageNode",
+    "generate:api:distributor-node": "yarn openapi-generator-cli generate -i ../../distributor-node/src/api-spec/openapi.yml -g typescript-axios -o ./src/apis/distributorNode",
+    "generate:all": "yarn generate:api:storage-node && yarn generate:api:distributor-node"
   },
   "dependencies": {
     "@apollo/client": "^3.2.5",
@@ -23,7 +26,11 @@
     "bn.js": "^4.11.8",
     "dotenv": "^8.2.0",
     "fs": "^0.0.1-security",
-    "uuid": "^7.0.3"
+    "uuid": "^7.0.3",
+    "axios": "^0.21.1",
+    "bmp-js": "^0.1.0",
+    "@types/bmp-js": "^0.1.0",
+    "node-cleanup": "^2.1.2"
   },
   "devDependencies": {
     "@polkadot/ts": "^0.4.8",
@@ -32,7 +39,8 @@
     "chai": "^4.2.0",
     "prettier": "^2.2.1",
     "ts-node": "^10.2.1",
-    "typescript": "^4.4.3"
+    "typescript": "^4.4.3",
+    "@openapitools/openapi-generator-cli": "^2.3.6"
   },
   "volta": {
     "extends": "../../package.json"

+ 0 - 50
tests/network-tests/run-storage-node-tests.sh

@@ -1,50 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
-cd $SCRIPT_PATH
-
-set -a
-. ../../.env
-set +a
-
-function cleanup() {
-    # Show tail end of logs for the processor and indexer containers to
-    # see any possible errors
-    (echo "## Processor Logs ##" && docker logs joystream_processor_1 --tail 50) || :
-    (echo "## Indexer Logs ##" && docker logs joystream_indexer_1 --tail 50) || :
-    docker-compose down -v
-}
-
-trap cleanup EXIT
-
-# clean start
-docker-compose down -v
-
-docker-compose up -d joystream-node
-
-# Storage node
-DEBUG=joystream:storage-cli:dev yarn storage-cli dev-init
-docker-compose up -d colossus
-
-# Query node is expected to have been already built
-docker-compose up -d db
-yarn workspace query-node-root db:migrate
-docker-compose up -d graphql-server
-# Starting up processor will bring up all services it depends on
-docker-compose up -d processor
-
-# Fixes Error: No active storage providers available
-echo "Wait for colossus to announce public url"
-sleep 6
-
-echo "Creating channel..."
-yarn joystream-cli media:createChannel \
-  --input ./assets/TestChannel.json --confirm
-
-echo "Uploading video..."
-yes | yarn joystream-cli media:uploadVideo ./assets/joystream.MOV \
-  --input ./assets/TestVideo.json \
-  --confirm 
-
-time DEBUG=* yarn workspace network-tests run-test-scenario storage-node

+ 10 - 2
tests/network-tests/run-tests.sh

@@ -54,7 +54,11 @@ docker run --rm -v ${DATA_PATH}:/data joystream/node:${RUNTIME} build-spec \
   --raw --disable-default-bootnode \
   --chain /data/chain-spec.json > ~/tmp/chain-spec-raw.json
 
-NETWORK_ARG=
+# Initialize joystream_default network if not already up
+docker network create joystream_default || true
+
+# Attach to joystream_default network by default
+NETWORK_ARG="--network joystream_default --network-alias joystream-node"
 if [ "$ATTACH_TO_NETWORK" != "" ]; then
   NETWORK_ARG="--network ${ATTACH_TO_NETWORK}"
 fi
@@ -69,6 +73,7 @@ function cleanup() {
     docker logs ${CONTAINER_ID} --tail 15
     docker stop ${CONTAINER_ID}
     docker rm ${CONTAINER_ID}
+    yarn workspace query-node-root kill
 }
 
 trap cleanup EXIT
@@ -99,4 +104,7 @@ fi
 # Display runtime version
 yarn workspace api-scripts tsnode-strict src/status.ts | grep Runtime
 
-# ./run-test-scenario.sh $1
+# Start the query-node
+yarn workspace query-node-root start
+
+./run-test-scenario.sh $1

+ 33 - 16
tests/network-tests/src/Api.ts

@@ -13,7 +13,7 @@ import {
   Opening as WorkingGroupOpening,
 } from '@joystream/types/working-group'
 import { ElectionStake, Seat } from '@joystream/types/council'
-import { AccountInfo, Balance, BalanceOf, BlockNumber, EventRecord } from '@polkadot/types/interfaces'
+import { AccountInfo, Balance, BalanceOf, BlockNumber, EventRecord, AccountId } from '@polkadot/types/interfaces'
 import BN from 'bn.js'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
 import { Sender, LogLevel } from './sender'
@@ -30,13 +30,19 @@ import {
 } from '@joystream/types/hiring'
 import { FillOpeningParameters, ProposalId } from '@joystream/types/proposals'
 import { v4 as uuid } from 'uuid'
-import { ContentId, DataObject } from '@joystream/types/storage'
 import { extendDebug } from './Debugger'
 import { InvertedPromise } from './InvertedPromise'
 
 export enum WorkingGroups {
+  DistributionWorkingGroup = 'distributionWorkingGroup',
   StorageWorkingGroup = 'storageWorkingGroup',
-  ContentDirectoryWorkingGroup = 'contentDirectoryWorkingGroup',
+  ContentWorkingGroup = 'contentWorkingGroup',
+}
+
+const workingGroupNameByGroup: { [key in WorkingGroups]: string } = {
+  [WorkingGroups.DistributionWorkingGroup]: 'Distribution',
+  [WorkingGroups.StorageWorkingGroup]: 'Storage',
+  [WorkingGroups.ContentWorkingGroup]: 'Content',
 }
 
 export class ApiFactory {
@@ -91,6 +97,9 @@ export class ApiFactory {
   // }
 }
 
+// Global suri by accountId map
+const suriByAccountId = new Map<string, string>()
+
 export class Api {
   private readonly api: ApiPromise
   private readonly sender: Sender
@@ -105,6 +114,18 @@ export class Api {
     this.sender = new Sender(api, keyring, label)
   }
 
+  getSuri(address: string | AccountId): string {
+    const suri = suriByAccountId.get(address.toString())
+    if (!suri) {
+      throw new Error(`Suri not found for account: ${address}`)
+    }
+    return suri
+  }
+
+  getKeypair(address: string | AccountId): KeyringPair {
+    return this.keyring.getPair(address)
+  }
+
   public enableDebugTxLogs(): void {
     this.sender.setLogLevel(LogLevel.Debug)
   }
@@ -116,21 +137,17 @@ export class Api {
   public createKeyPairs(n: number): KeyringPair[] {
     const nKeyPairs: KeyringPair[] = []
     for (let i = 0; i < n; i++) {
-      nKeyPairs.push(this.keyring.addFromUri(i + uuid().substring(0, 8)))
+      const suri = i + uuid().substring(0, 8)
+      const pair = this.keyring.addFromUri(suri)
+      nKeyPairs.push(pair)
+      suriByAccountId.set(pair.address, suri)
     }
     return nKeyPairs
   }
 
   // Well known WorkingGroup enum defined in runtime
   public getWorkingGroupString(workingGroup: WorkingGroups): string {
-    switch (workingGroup) {
-      case WorkingGroups.StorageWorkingGroup:
-        return 'Storage'
-      case WorkingGroups.ContentDirectoryWorkingGroup:
-        return 'Content'
-      default:
-        throw new Error(`Invalid working group string representation: ${workingGroup}`)
-    }
+    return workingGroupNameByGroup[workingGroup]
   }
 
   public async makeSudoCall(tx: SubmittableExtrinsic<'promise'>): Promise<ISubmittableResult> {
@@ -1699,8 +1716,8 @@ export class Api {
     return this.api.createType('u32', this.api.consts[module].maxWorkerNumberLimit)
   }
 
-  async getDataByContentId(contentId: ContentId): Promise<DataObject | null> {
-    const dataObject = await this.api.query.dataDirectory.dataByContentId<Option<DataObject>>(contentId)
-    return dataObject.unwrapOr(null)
-  }
+  // async getDataByContentId(contentId: ContentId): Promise<DataObject | null> {
+  //   const dataObject = await this.api.query.dataDirectory.dataByContentId<Option<DataObject>>(contentId)
+  //   return dataObject.unwrapOr(null)
+  // }
 }

+ 27 - 0
tests/network-tests/src/apis/distributorNode/.openapi-generator-ignore

@@ -0,0 +1,27 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
+
+git_push.sh
+.npmignore
+.gitignore

+ 9 - 0
tests/network-tests/src/apis/distributorNode/.openapi-generator/FILES

@@ -0,0 +1,9 @@
+.gitignore
+.npmignore
+.openapi-generator-ignore
+api.ts
+base.ts
+common.ts
+configuration.ts
+git_push.sh
+index.ts

+ 1 - 0
tests/network-tests/src/apis/distributorNode/.openapi-generator/VERSION

@@ -0,0 +1 @@
+5.2.1

+ 394 - 0
tests/network-tests/src/apis/distributorNode/api.ts

@@ -0,0 +1,394 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Distributor node API
+ * Distributor node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from './configuration';
+import globalAxios, { AxiosPromise, AxiosInstance } from 'axios';
+// Some imports not used depending on template conditions
+// @ts-ignore
+import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
+// @ts-ignore
+import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
+
+/**
+ * @type BucketsResponse
+ * @export
+ */
+export type BucketsResponse = BucketsResponseOneOf | BucketsResponseOneOf1;
+
+/**
+ * 
+ * @export
+ * @interface BucketsResponseOneOf
+ */
+export interface BucketsResponseOneOf {
+    /**
+     * 
+     * @type {Array<number>}
+     * @memberof BucketsResponseOneOf
+     */
+    bucketIds: Array<number>;
+}
+/**
+ * 
+ * @export
+ * @interface BucketsResponseOneOf1
+ */
+export interface BucketsResponseOneOf1 {
+    /**
+     * 
+     * @type {number}
+     * @memberof BucketsResponseOneOf1
+     */
+    allByWorkerId: number;
+}
+/**
+ * 
+ * @export
+ * @interface ErrorResponse
+ */
+export interface ErrorResponse {
+    /**
+     * 
+     * @type {string}
+     * @memberof ErrorResponse
+     */
+    type?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ErrorResponse
+     */
+    message: string;
+}
+/**
+ * 
+ * @export
+ * @interface StatusResponse
+ */
+export interface StatusResponse {
+    /**
+     * 
+     * @type {string}
+     * @memberof StatusResponse
+     */
+    id: string;
+    /**
+     * 
+     * @type {number}
+     * @memberof StatusResponse
+     */
+    objectsInCache: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof StatusResponse
+     */
+    storageLimit: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof StatusResponse
+     */
+    storageUsed: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof StatusResponse
+     */
+    uptime: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof StatusResponse
+     */
+    downloadsInProgress: number;
+}
+
+/**
+ * PublicApi - axios parameter creator
+ * @export
+ */
+export const PublicApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * Returns a media file.
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicAsset: async (objectId: string, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'objectId' is not null or undefined
+            assertParamExists('publicAsset', 'objectId', objectId)
+            const localVarPath = `/asset/{objectId}`
+                .replace(`{${"objectId"}}`, encodeURIComponent(String(objectId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns asset response headers (cache status, content type and/or length, accepted ranges etc.)
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicAssetHead: async (objectId: string, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'objectId' is not null or undefined
+            assertParamExists('publicAssetHead', 'objectId', objectId)
+            const localVarPath = `/asset/{objectId}`
+                .replace(`{${"objectId"}}`, encodeURIComponent(String(objectId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'HEAD', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns list of distributed buckets
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicBuckets: async (options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/buckets`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns json object describing current node status.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicStatus: async (options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/status`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * PublicApi - functional programming interface
+ * @export
+ */
+export const PublicApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = PublicApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * Returns a media file.
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicAsset(objectId: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicAsset(objectId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns asset response headers (cache status, content type and/or length, accepted ranges etc.)
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicAssetHead(objectId: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicAssetHead(objectId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns list of distributed buckets
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicBuckets(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<BucketsResponse>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicBuckets(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns json object describing current node status.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicStatus(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusResponse>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicStatus(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * PublicApi - factory interface
+ * @export
+ */
+export const PublicApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = PublicApiFp(configuration)
+    return {
+        /**
+         * Returns a media file.
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicAsset(objectId: string, options?: any): AxiosPromise<any> {
+            return localVarFp.publicAsset(objectId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns asset response headers (cache status, content type and/or length, accepted ranges etc.)
+         * @param {string} objectId Data Object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicAssetHead(objectId: string, options?: any): AxiosPromise<void> {
+            return localVarFp.publicAssetHead(objectId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns list of distributed buckets
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicBuckets(options?: any): AxiosPromise<BucketsResponse> {
+            return localVarFp.publicBuckets(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns json object describing current node status.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicStatus(options?: any): AxiosPromise<StatusResponse> {
+            return localVarFp.publicStatus(options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * PublicApi - object-oriented interface
+ * @export
+ * @class PublicApi
+ * @extends {BaseAPI}
+ */
+export class PublicApi extends BaseAPI {
+    /**
+     * Returns a media file.
+     * @param {string} objectId Data Object ID
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof PublicApi
+     */
+    public publicAsset(objectId: string, options?: any) {
+        return PublicApiFp(this.configuration).publicAsset(objectId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns asset response headers (cache status, content type and/or length, accepted ranges etc.)
+     * @param {string} objectId Data Object ID
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof PublicApi
+     */
+    public publicAssetHead(objectId: string, options?: any) {
+        return PublicApiFp(this.configuration).publicAssetHead(objectId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns list of distributed buckets
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof PublicApi
+     */
+    public publicBuckets(options?: any) {
+        return PublicApiFp(this.configuration).publicBuckets(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns json object describing current node status.
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof PublicApi
+     */
+    public publicStatus(options?: any) {
+        return PublicApiFp(this.configuration).publicStatus(options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+

+ 71 - 0
tests/network-tests/src/apis/distributorNode/base.ts

@@ -0,0 +1,71 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Distributor node API
+ * Distributor node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from "./configuration";
+// Some imports not used depending on template conditions
+// @ts-ignore
+import globalAxios, { AxiosPromise, AxiosInstance } from 'axios';
+
+export const BASE_PATH = "http://localhost:3334/api/v1".replace(/\/+$/, "");
+
+/**
+ *
+ * @export
+ */
+export const COLLECTION_FORMATS = {
+    csv: ",",
+    ssv: " ",
+    tsv: "\t",
+    pipes: "|",
+};
+
+/**
+ *
+ * @export
+ * @interface RequestArgs
+ */
+export interface RequestArgs {
+    url: string;
+    options: any;
+}
+
+/**
+ *
+ * @export
+ * @class BaseAPI
+ */
+export class BaseAPI {
+    protected configuration: Configuration | undefined;
+
+    constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
+        if (configuration) {
+            this.configuration = configuration;
+            this.basePath = configuration.basePath || this.basePath;
+        }
+    }
+};
+
+/**
+ *
+ * @export
+ * @class RequiredError
+ * @extends {Error}
+ */
+export class RequiredError extends Error {
+    name: "RequiredError" = "RequiredError";
+    constructor(public field: string, msg?: string) {
+        super(msg);
+    }
+}

+ 138 - 0
tests/network-tests/src/apis/distributorNode/common.ts

@@ -0,0 +1,138 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Distributor node API
+ * Distributor node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from "./configuration";
+import { RequiredError, RequestArgs } from "./base";
+import { AxiosInstance } from 'axios';
+
+/**
+ *
+ * @export
+ */
+export const DUMMY_BASE_URL = 'https://example.com'
+
+/**
+ *
+ * @throws {RequiredError}
+ * @export
+ */
+export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
+    if (paramValue === null || paramValue === undefined) {
+        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
+    if (configuration && configuration.apiKey) {
+        const localVarApiKeyValue = typeof configuration.apiKey === 'function'
+            ? await configuration.apiKey(keyParamName)
+            : await configuration.apiKey;
+        object[keyParamName] = localVarApiKeyValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
+    if (configuration && (configuration.username || configuration.password)) {
+        object["auth"] = { username: configuration.username, password: configuration.password };
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const accessToken = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken()
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + accessToken;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken(name, scopes)
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + localVarAccessTokenValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setSearchParams = function (url: URL, ...objects: any[]) {
+    const searchParams = new URLSearchParams(url.search);
+    for (const object of objects) {
+        for (const key in object) {
+            if (Array.isArray(object[key])) {
+                searchParams.delete(key);
+                for (const item of object[key]) {
+                    searchParams.append(key, item);
+                }
+            } else {
+                searchParams.set(key, object[key]);
+            }
+        }
+    }
+    url.search = searchParams.toString();
+}
+
+/**
+ *
+ * @export
+ */
+export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
+    const nonString = typeof value !== 'string';
+    const needsSerialization = nonString && configuration && configuration.isJsonMime
+        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])
+        : nonString;
+    return needsSerialization
+        ? JSON.stringify(value !== undefined ? value : {})
+        : (value || "");
+}
+
+/**
+ *
+ * @export
+ */
+export const toPathString = function (url: URL) {
+    return url.pathname + url.search + url.hash
+}
+
+/**
+ *
+ * @export
+ */
+export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
+    return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+        const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
+        return axios.request(axiosRequestArgs);
+    };
+}

+ 101 - 0
tests/network-tests/src/apis/distributorNode/configuration.ts

@@ -0,0 +1,101 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Distributor node API
+ * Distributor node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface ConfigurationParameters {
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    username?: string;
+    password?: string;
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    basePath?: string;
+    baseOptions?: any;
+    formDataCtor?: new () => any;
+}
+
+export class Configuration {
+    /**
+     * parameter for apiKey security
+     * @param name security name
+     * @memberof Configuration
+     */
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    username?: string;
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    password?: string;
+    /**
+     * parameter for oauth2 security
+     * @param name security name
+     * @param scopes oauth2 scope
+     * @memberof Configuration
+     */
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    /**
+     * override base path
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    basePath?: string;
+    /**
+     * base options for axios calls
+     *
+     * @type {any}
+     * @memberof Configuration
+     */
+    baseOptions?: any;
+    /**
+     * The FormData constructor that will be used to create multipart form data
+     * requests. You can inject this here so that execution environments that
+     * do not support the FormData class can still run the generated client.
+     *
+     * @type {new () => FormData}
+     */
+    formDataCtor?: new () => any;
+
+    constructor(param: ConfigurationParameters = {}) {
+        this.apiKey = param.apiKey;
+        this.username = param.username;
+        this.password = param.password;
+        this.accessToken = param.accessToken;
+        this.basePath = param.basePath;
+        this.baseOptions = param.baseOptions;
+        this.formDataCtor = param.formDataCtor;
+    }
+
+    /**
+     * Check if the given MIME is a JSON MIME.
+     * JSON MIME examples:
+     *   application/json
+     *   application/json; charset=UTF8
+     *   APPLICATION/JSON
+     *   application/vnd.company+json
+     * @param mime - MIME (Multipurpose Internet Mail Extensions)
+     * @return True if the given MIME is JSON, false otherwise.
+     */
+    public isJsonMime(mime: string): boolean {
+        const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
+        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
+    }
+}

+ 18 - 0
tests/network-tests/src/apis/distributorNode/index.ts

@@ -0,0 +1,18 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Distributor node API
+ * Distributor node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export * from "./api";
+export * from "./configuration";
+

+ 27 - 0
tests/network-tests/src/apis/storageNode/.openapi-generator-ignore

@@ -0,0 +1,27 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
+
+git_push.sh
+.npmignore
+.gitignore

+ 5 - 0
tests/network-tests/src/apis/storageNode/.openapi-generator/FILES

@@ -0,0 +1,5 @@
+api.ts
+base.ts
+common.ts
+configuration.ts
+index.ts

+ 1 - 0
tests/network-tests/src/apis/storageNode/.openapi-generator/VERSION

@@ -0,0 +1 @@
+5.2.1

+ 772 - 0
tests/network-tests/src/apis/storageNode/api.ts

@@ -0,0 +1,772 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Storage node API
+ * Storage node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from './configuration';
+import globalAxios, { AxiosPromise, AxiosInstance } from 'axios';
+// Some imports not used depending on template conditions
+// @ts-ignore
+import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
+// @ts-ignore
+import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
+
+/**
+ * 
+ * @export
+ * @interface DataStatsResponse
+ */
+export interface DataStatsResponse {
+    /**
+     * 
+     * @type {number}
+     * @memberof DataStatsResponse
+     */
+    totalSize: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof DataStatsResponse
+     */
+    objectNumber: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof DataStatsResponse
+     */
+    tempDirSize?: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof DataStatsResponse
+     */
+    tempDownloads?: number;
+}
+/**
+ * 
+ * @export
+ * @interface ErrorResponse
+ */
+export interface ErrorResponse {
+    /**
+     * 
+     * @type {string}
+     * @memberof ErrorResponse
+     */
+    type?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ErrorResponse
+     */
+    message: string;
+}
+/**
+ * 
+ * @export
+ * @interface InlineResponse201
+ */
+export interface InlineResponse201 {
+    /**
+     * 
+     * @type {string}
+     * @memberof InlineResponse201
+     */
+    id?: string;
+}
+/**
+ * 
+ * @export
+ * @interface InlineResponse2011
+ */
+export interface InlineResponse2011 {
+    /**
+     * 
+     * @type {string}
+     * @memberof InlineResponse2011
+     */
+    token?: string;
+}
+/**
+ * 
+ * @export
+ * @interface TokenRequest
+ */
+export interface TokenRequest {
+    /**
+     * 
+     * @type {TokenRequestData}
+     * @memberof TokenRequest
+     */
+    data: TokenRequestData;
+    /**
+     * 
+     * @type {string}
+     * @memberof TokenRequest
+     */
+    signature: string;
+}
+/**
+ * 
+ * @export
+ * @interface TokenRequestData
+ */
+export interface TokenRequestData {
+    /**
+     * 
+     * @type {number}
+     * @memberof TokenRequestData
+     */
+    memberId: number;
+    /**
+     * 
+     * @type {string}
+     * @memberof TokenRequestData
+     */
+    accountId: string;
+    /**
+     * 
+     * @type {number}
+     * @memberof TokenRequestData
+     */
+    dataObjectId: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof TokenRequestData
+     */
+    storageBucketId: number;
+    /**
+     * 
+     * @type {string}
+     * @memberof TokenRequestData
+     */
+    bagId: string;
+}
+/**
+ * 
+ * @export
+ * @interface VersionResponse
+ */
+export interface VersionResponse {
+    /**
+     * 
+     * @type {string}
+     * @memberof VersionResponse
+     */
+    version: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof VersionResponse
+     */
+    userAgent?: string;
+}
+
+/**
+ * FilesApi - axios parameter creator
+ * @export
+ */
+export const FilesApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * Get auth token from a server.
+         * @param {TokenRequest} [tokenRequest] Token request parameters,
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiAuthTokenForUploading: async (tokenRequest?: TokenRequest, options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/authToken`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(tokenRequest, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns a media file.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiGetFile: async (id: string, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'id' is not null or undefined
+            assertParamExists('publicApiGetFile', 'id', id)
+            const localVarPath = `/files/{id}`
+                .replace(`{${"id"}}`, encodeURIComponent(String(id)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns a media file headers.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiGetFileHeaders: async (id: string, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'id' is not null or undefined
+            assertParamExists('publicApiGetFileHeaders', 'id', id)
+            const localVarPath = `/files/{id}`
+                .replace(`{${"id"}}`, encodeURIComponent(String(id)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'HEAD', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Upload data
+         * @param {string} dataObjectId Data object runtime ID
+         * @param {string} storageBucketId Storage bucket ID
+         * @param {string} bagId Bag ID
+         * @param {any} [file] Data file
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiUploadFile: async (dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'dataObjectId' is not null or undefined
+            assertParamExists('publicApiUploadFile', 'dataObjectId', dataObjectId)
+            // verify required parameter 'storageBucketId' is not null or undefined
+            assertParamExists('publicApiUploadFile', 'storageBucketId', storageBucketId)
+            // verify required parameter 'bagId' is not null or undefined
+            assertParamExists('publicApiUploadFile', 'bagId', bagId)
+            const localVarPath = `/files`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+            const localVarFormParams = new ((configuration && configuration.formDataCtor) || FormData)();
+
+            // authentication UploadAuth required
+            await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
+
+
+            if (file !== undefined) { 
+                localVarFormParams.append('file', file as any);
+            }
+    
+            if (dataObjectId !== undefined) { 
+                localVarFormParams.append('dataObjectId', dataObjectId as any);
+            }
+    
+            if (storageBucketId !== undefined) { 
+                localVarFormParams.append('storageBucketId', storageBucketId as any);
+            }
+    
+            if (bagId !== undefined) { 
+                localVarFormParams.append('bagId', bagId as any);
+            }
+    
+    
+            localVarHeaderParameter['Content-Type'] = 'multipart/form-data';
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = localVarFormParams;
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * FilesApi - functional programming interface
+ * @export
+ */
+export const FilesApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = FilesApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * Get auth token from a server.
+         * @param {TokenRequest} [tokenRequest] Token request parameters,
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2011>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiAuthTokenForUploading(tokenRequest, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns a media file.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicApiGetFile(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiGetFile(id, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns a media file headers.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicApiGetFileHeaders(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiGetFileHeaders(id, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Upload data
+         * @param {string} dataObjectId Data object runtime ID
+         * @param {string} storageBucketId Storage bucket ID
+         * @param {string} bagId Bag ID
+         * @param {any} [file] Data file
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async publicApiUploadFile(dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse201>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiUploadFile(dataObjectId, storageBucketId, bagId, file, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * FilesApi - factory interface
+ * @export
+ */
+export const FilesApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = FilesApiFp(configuration)
+    return {
+        /**
+         * Get auth token from a server.
+         * @param {TokenRequest} [tokenRequest] Token request parameters,
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any): AxiosPromise<InlineResponse2011> {
+            return localVarFp.publicApiAuthTokenForUploading(tokenRequest, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns a media file.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiGetFile(id: string, options?: any): AxiosPromise<any> {
+            return localVarFp.publicApiGetFile(id, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns a media file headers.
+         * @param {string} id Data object ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiGetFileHeaders(id: string, options?: any): AxiosPromise<void> {
+            return localVarFp.publicApiGetFileHeaders(id, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Upload data
+         * @param {string} dataObjectId Data object runtime ID
+         * @param {string} storageBucketId Storage bucket ID
+         * @param {string} bagId Bag ID
+         * @param {any} [file] Data file
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        publicApiUploadFile(dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options?: any): AxiosPromise<InlineResponse201> {
+            return localVarFp.publicApiUploadFile(dataObjectId, storageBucketId, bagId, file, options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * FilesApi - object-oriented interface
+ * @export
+ * @class FilesApi
+ * @extends {BaseAPI}
+ */
+export class FilesApi extends BaseAPI {
+    /**
+     * Get auth token from a server.
+     * @param {TokenRequest} [tokenRequest] Token request parameters,
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof FilesApi
+     */
+    public publicApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any) {
+        return FilesApiFp(this.configuration).publicApiAuthTokenForUploading(tokenRequest, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns a media file.
+     * @param {string} id Data object ID
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof FilesApi
+     */
+    public publicApiGetFile(id: string, options?: any) {
+        return FilesApiFp(this.configuration).publicApiGetFile(id, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns a media file headers.
+     * @param {string} id Data object ID
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof FilesApi
+     */
+    public publicApiGetFileHeaders(id: string, options?: any) {
+        return FilesApiFp(this.configuration).publicApiGetFileHeaders(id, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Upload data
+     * @param {string} dataObjectId Data object runtime ID
+     * @param {string} storageBucketId Storage bucket ID
+     * @param {string} bagId Bag ID
+     * @param {any} [file] Data file
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof FilesApi
+     */
+    public publicApiUploadFile(dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options?: any) {
+        return FilesApiFp(this.configuration).publicApiUploadFile(dataObjectId, storageBucketId, bagId, file, options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+
+/**
+ * StateApi - axios parameter creator
+ * @export
+ */
+export const StateApiAxiosParamCreator = function (configuration?: Configuration) {
+    return {
+        /**
+         * Returns all local data objects.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetAllLocalDataObjects: async (options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/state/data-objects`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns local data objects for the bag.
+         * @param {string} bagId Bag ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetLocalDataObjectsByBagId: async (bagId: string, options: any = {}): Promise<RequestArgs> => {
+            // verify required parameter 'bagId' is not null or undefined
+            assertParamExists('stateApiGetLocalDataObjectsByBagId', 'bagId', bagId)
+            const localVarPath = `/state/bags/{bagId}/data-objects`
+                .replace(`{${"bagId"}}`, encodeURIComponent(String(bagId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns local uploading directory stats.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetLocalDataStats: async (options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/state/data`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * Returns server version.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetVersion: async (options: any = {}): Promise<RequestArgs> => {
+            const localVarPath = `/version`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+    }
+};
+
+/**
+ * StateApi - functional programming interface
+ * @export
+ */
+export const StateApiFp = function(configuration?: Configuration) {
+    const localVarAxiosParamCreator = StateApiAxiosParamCreator(configuration)
+    return {
+        /**
+         * Returns all local data objects.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async stateApiGetAllLocalDataObjects(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.stateApiGetAllLocalDataObjects(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns local data objects for the bag.
+         * @param {string} bagId Bag ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async stateApiGetLocalDataObjectsByBagId(bagId: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.stateApiGetLocalDataObjectsByBagId(bagId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns local uploading directory stats.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async stateApiGetLocalDataStats(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DataStatsResponse>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.stateApiGetLocalDataStats(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+        /**
+         * Returns server version.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async stateApiGetVersion(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<VersionResponse>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.stateApiGetVersion(options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
+    }
+};
+
+/**
+ * StateApi - factory interface
+ * @export
+ */
+export const StateApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+    const localVarFp = StateApiFp(configuration)
+    return {
+        /**
+         * Returns all local data objects.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetAllLocalDataObjects(options?: any): AxiosPromise<Array<string>> {
+            return localVarFp.stateApiGetAllLocalDataObjects(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns local data objects for the bag.
+         * @param {string} bagId Bag ID
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetLocalDataObjectsByBagId(bagId: string, options?: any): AxiosPromise<Array<string>> {
+            return localVarFp.stateApiGetLocalDataObjectsByBagId(bagId, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns local uploading directory stats.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetLocalDataStats(options?: any): AxiosPromise<DataStatsResponse> {
+            return localVarFp.stateApiGetLocalDataStats(options).then((request) => request(axios, basePath));
+        },
+        /**
+         * Returns server version.
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        stateApiGetVersion(options?: any): AxiosPromise<VersionResponse> {
+            return localVarFp.stateApiGetVersion(options).then((request) => request(axios, basePath));
+        },
+    };
+};
+
+/**
+ * StateApi - object-oriented interface
+ * @export
+ * @class StateApi
+ * @extends {BaseAPI}
+ */
+export class StateApi extends BaseAPI {
+    /**
+     * Returns all local data objects.
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof StateApi
+     */
+    public stateApiGetAllLocalDataObjects(options?: any) {
+        return StateApiFp(this.configuration).stateApiGetAllLocalDataObjects(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns local data objects for the bag.
+     * @param {string} bagId Bag ID
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof StateApi
+     */
+    public stateApiGetLocalDataObjectsByBagId(bagId: string, options?: any) {
+        return StateApiFp(this.configuration).stateApiGetLocalDataObjectsByBagId(bagId, options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns local uploading directory stats.
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof StateApi
+     */
+    public stateApiGetLocalDataStats(options?: any) {
+        return StateApiFp(this.configuration).stateApiGetLocalDataStats(options).then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     * Returns server version.
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof StateApi
+     */
+    public stateApiGetVersion(options?: any) {
+        return StateApiFp(this.configuration).stateApiGetVersion(options).then((request) => request(this.axios, this.basePath));
+    }
+}
+
+

+ 71 - 0
tests/network-tests/src/apis/storageNode/base.ts

@@ -0,0 +1,71 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Storage node API
+ * Storage node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from "./configuration";
+// Some imports not used depending on template conditions
+// @ts-ignore
+import globalAxios, { AxiosPromise, AxiosInstance } from 'axios';
+
+export const BASE_PATH = "http://localhost:3333/api/v1".replace(/\/+$/, "");
+
+/**
+ *
+ * @export
+ */
+export const COLLECTION_FORMATS = {
+    csv: ",",
+    ssv: " ",
+    tsv: "\t",
+    pipes: "|",
+};
+
+/**
+ *
+ * @export
+ * @interface RequestArgs
+ */
+export interface RequestArgs {
+    url: string;
+    options: any;
+}
+
+/**
+ *
+ * @export
+ * @class BaseAPI
+ */
+export class BaseAPI {
+    protected configuration: Configuration | undefined;
+
+    constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
+        if (configuration) {
+            this.configuration = configuration;
+            this.basePath = configuration.basePath || this.basePath;
+        }
+    }
+};
+
+/**
+ *
+ * @export
+ * @class RequiredError
+ * @extends {Error}
+ */
+export class RequiredError extends Error {
+    name: "RequiredError" = "RequiredError";
+    constructor(public field: string, msg?: string) {
+        super(msg);
+    }
+}

+ 138 - 0
tests/network-tests/src/apis/storageNode/common.ts

@@ -0,0 +1,138 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Storage node API
+ * Storage node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import { Configuration } from "./configuration";
+import { RequiredError, RequestArgs } from "./base";
+import { AxiosInstance } from 'axios';
+
+/**
+ *
+ * @export
+ */
+export const DUMMY_BASE_URL = 'https://example.com'
+
+/**
+ *
+ * @throws {RequiredError}
+ * @export
+ */
+export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
+    if (paramValue === null || paramValue === undefined) {
+        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
+    if (configuration && configuration.apiKey) {
+        const localVarApiKeyValue = typeof configuration.apiKey === 'function'
+            ? await configuration.apiKey(keyParamName)
+            : await configuration.apiKey;
+        object[keyParamName] = localVarApiKeyValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
+    if (configuration && (configuration.username || configuration.password)) {
+        object["auth"] = { username: configuration.username, password: configuration.password };
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const accessToken = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken()
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + accessToken;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
+    if (configuration && configuration.accessToken) {
+        const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
+            ? await configuration.accessToken(name, scopes)
+            : await configuration.accessToken;
+        object["Authorization"] = "Bearer " + localVarAccessTokenValue;
+    }
+}
+
+/**
+ *
+ * @export
+ */
+export const setSearchParams = function (url: URL, ...objects: any[]) {
+    const searchParams = new URLSearchParams(url.search);
+    for (const object of objects) {
+        for (const key in object) {
+            if (Array.isArray(object[key])) {
+                searchParams.delete(key);
+                for (const item of object[key]) {
+                    searchParams.append(key, item);
+                }
+            } else {
+                searchParams.set(key, object[key]);
+            }
+        }
+    }
+    url.search = searchParams.toString();
+}
+
+/**
+ *
+ * @export
+ */
+export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
+    const nonString = typeof value !== 'string';
+    const needsSerialization = nonString && configuration && configuration.isJsonMime
+        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])
+        : nonString;
+    return needsSerialization
+        ? JSON.stringify(value !== undefined ? value : {})
+        : (value || "");
+}
+
+/**
+ *
+ * @export
+ */
+export const toPathString = function (url: URL) {
+    return url.pathname + url.search + url.hash
+}
+
+/**
+ *
+ * @export
+ */
+export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
+    return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+        const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
+        return axios.request(axiosRequestArgs);
+    };
+}

+ 101 - 0
tests/network-tests/src/apis/storageNode/configuration.ts

@@ -0,0 +1,101 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Storage node API
+ * Storage node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export interface ConfigurationParameters {
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    username?: string;
+    password?: string;
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    basePath?: string;
+    baseOptions?: any;
+    formDataCtor?: new () => any;
+}
+
+export class Configuration {
+    /**
+     * parameter for apiKey security
+     * @param name security name
+     * @memberof Configuration
+     */
+    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    username?: string;
+    /**
+     * parameter for basic security
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    password?: string;
+    /**
+     * parameter for oauth2 security
+     * @param name security name
+     * @param scopes oauth2 scope
+     * @memberof Configuration
+     */
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
+    /**
+     * override base path
+     *
+     * @type {string}
+     * @memberof Configuration
+     */
+    basePath?: string;
+    /**
+     * base options for axios calls
+     *
+     * @type {any}
+     * @memberof Configuration
+     */
+    baseOptions?: any;
+    /**
+     * The FormData constructor that will be used to create multipart form data
+     * requests. You can inject this here so that execution environments that
+     * do not support the FormData class can still run the generated client.
+     *
+     * @type {new () => FormData}
+     */
+    formDataCtor?: new () => any;
+
+    constructor(param: ConfigurationParameters = {}) {
+        this.apiKey = param.apiKey;
+        this.username = param.username;
+        this.password = param.password;
+        this.accessToken = param.accessToken;
+        this.basePath = param.basePath;
+        this.baseOptions = param.baseOptions;
+        this.formDataCtor = param.formDataCtor;
+    }
+
+    /**
+     * Check if the given MIME is a JSON MIME.
+     * JSON MIME examples:
+     *   application/json
+     *   application/json; charset=UTF8
+     *   APPLICATION/JSON
+     *   application/vnd.company+json
+     * @param mime - MIME (Multipurpose Internet Mail Extensions)
+     * @return True if the given MIME is JSON, false otherwise.
+     */
+    public isJsonMime(mime: string): boolean {
+        const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
+        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
+    }
+}

+ 18 - 0
tests/network-tests/src/apis/storageNode/index.ts

@@ -0,0 +1,18 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Storage node API
+ * Storage node API
+ *
+ * The version of the OpenAPI document: 0.1.0
+ * Contact: info@joystream.org
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export * from "./api";
+export * from "./configuration";
+

+ 33 - 0
tests/network-tests/src/cli/base.ts

@@ -0,0 +1,33 @@
+import path from 'path'
+import { execFile } from 'child_process'
+import { promisify } from 'util'
+import { Sender } from '../sender'
+
+export type CommandResult = { stdout: string; stderr: string; out: string }
+
+export abstract class CLI {
+  protected env: Record<string, string>
+  protected readonly rootPath: string
+  protected readonly binPath: string
+  protected defaultArgs: string[]
+
+  constructor(rootPath: string, defaultEnv: Record<string, string> = {}, defaultArgs: string[] = []) {
+    this.rootPath = rootPath
+    this.binPath = path.resolve(rootPath, './bin/run')
+    this.env = {
+      ...process.env,
+      AUTO_CONFIRM: 'true',
+      ...defaultEnv,
+    }
+    this.defaultArgs = [...defaultArgs]
+  }
+
+  async run(command: string, args: string[] = []): Promise<CommandResult> {
+    const pExecFile = promisify(execFile)
+    const { env } = this
+    const { stdout, stderr } = await Sender.asyncLock.acquire('tx-queue', () =>
+      pExecFile(this.binPath, [command, ...this.defaultArgs, ...args], { env, cwd: this.rootPath })
+    )
+    return { stdout, stderr, out: stdout.trim() }
+  }
+}

+ 38 - 0
tests/network-tests/src/cli/distributor.ts

@@ -0,0 +1,38 @@
+import path from 'path'
+import { spawn } from 'child_process'
+import { DistributorNodeConfiguration } from '@joystream/distributor-cli/src/types/generated/ConfigJson'
+import { CLI } from './base'
+import { WorkerId } from '@joystream/types/working-group'
+import { ProcessManager } from './utils'
+
+const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../distributor-node')
+
+export class DistributorCLI extends CLI {
+  constructor(keyUris: string[]) {
+    const keys: DistributorNodeConfiguration['keys'] = keyUris.map((suri) => ({
+      suri,
+    })) as DistributorNodeConfiguration['keys']
+    const defaultEnv = {
+      JOYSTREAM_DISTRIBUTOR__KEYS: JSON.stringify(keys),
+    }
+    super(CLI_ROOT_PATH, defaultEnv)
+  }
+
+  async spawnServer(
+    operatorId: number | WorkerId,
+    port = 3334,
+    buckets: number[] | 'all' = 'all'
+  ): Promise<ProcessManager> {
+    const { env } = this
+    const serverEnv = {
+      ...env,
+      JOYSTREAM_DISTRIBUTOR__PORT: port.toString(),
+      JOYSTREAM_DISTRIBUTOR__WORKER_ID: operatorId.toString(),
+      JOYSTREAM_DISTRIBUTOR__BUCKETS: buckets === 'all' ? 'all' : JSON.stringify(buckets),
+    }
+    const serverProcess = spawn(this.binPath, ['start'], { env: serverEnv, cwd: this.rootPath })
+    const serverManager = new ProcessManager('Distributor node server', serverProcess, 'stdout')
+    await serverManager.untilOutput(`listening on port ${port}`)
+    return serverManager
+  }
+}

+ 23 - 0
tests/network-tests/src/cli/joystream.ts

@@ -0,0 +1,23 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import path from 'path'
+import { CLI, CommandResult } from './base'
+import { tmpJsonFile } from './utils'
+import { ChannelInputParameters } from '@joystream/cli/src/Types'
+
+const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../cli')
+
+export class JoystreamCLI extends CLI {
+  constructor() {
+    super(CLI_ROOT_PATH)
+  }
+
+  async importKey(pair: KeyringPair): Promise<CommandResult> {
+    const jsonFile = tmpJsonFile(pair.toJson())
+    return this.run('account:import', [jsonFile])
+  }
+
+  async createChannel(inputData: ChannelInputParameters, args: string[]): Promise<CommandResult> {
+    const jsonFile = tmpJsonFile(inputData)
+    return this.run('content:createChannel', ['--input', jsonFile, ...args])
+  }
+}

+ 55 - 0
tests/network-tests/src/cli/storage.ts

@@ -0,0 +1,55 @@
+import path from 'path'
+import { CLI } from './base'
+import { spawn } from 'child_process'
+import { v4 as uuid } from 'uuid'
+import { WorkerId } from '@joystream/types/working-group'
+import os from 'os'
+import { ProcessManager } from './utils'
+import fs from 'fs'
+
+const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../storage-node-v2')
+
+export class StorageCLI extends CLI {
+  constructor(defaultSuri?: string) {
+    super(CLI_ROOT_PATH, undefined, defaultSuri ? ['--accountUri', defaultSuri] : [])
+  }
+
+  setDefaultSuri(defaultSuri: string): void {
+    this.defaultArgs = ['--accountUri', defaultSuri]
+  }
+
+  async spawnServer(
+    operatorId: number | WorkerId,
+    port = 3333,
+    sync = true,
+    syncInterval = 1
+  ): Promise<ProcessManager> {
+    const queryNodeHost = new URL(process.env.QUERY_NODE_URL || '').host
+    const apiUrl = new URL(process.env.NODE_URL || '').toString()
+    const uploadsDir = path.join(os.tmpdir(), uuid())
+    fs.mkdirSync(uploadsDir)
+    const { env } = this
+    const args = [
+      ...this.defaultArgs,
+      '--worker',
+      operatorId.toString(),
+      '--port',
+      port.toString(),
+      '--queryNodeHost',
+      queryNodeHost,
+      '--apiUrl',
+      apiUrl,
+      '--uploads',
+      uploadsDir,
+    ]
+    if (sync) {
+      args.push('--sync')
+      args.push('--syncInterval')
+      args.push(syncInterval.toString())
+    }
+    const serverProcess = spawn(this.binPath, ['server', ...args], { env, cwd: this.rootPath })
+    const serverListener = new ProcessManager('Storage node server', serverProcess, 'stderr')
+    await serverListener.untilOutput('Listening')
+    return serverListener
+  }
+}

+ 111 - 0
tests/network-tests/src/cli/utils.ts

@@ -0,0 +1,111 @@
+import os from 'os'
+import fs from 'fs'
+import path from 'path'
+import { v4 as uuid } from 'uuid'
+import { ChildProcessWithoutNullStreams } from 'child_process'
+import { Utils } from '../utils'
+import _ from 'lodash'
+import bmp from 'bmp-js'
+import nodeCleanup from 'node-cleanup'
+
+export function tmpJsonFile(value: unknown): string {
+  const tmpFilePath = path.join(os.tmpdir(), `${uuid()}.json`)
+  fs.writeFileSync(tmpFilePath, JSON.stringify(value))
+  return tmpFilePath
+}
+
+export function randomImgFile(width: number, height: number): string {
+  const data = Buffer.from(Array.from({ length: width * height * 3 }, () => _.random(0, 255)))
+  const rawBmp = bmp.encode({ width, height, data })
+  const tmpFilePath = path.join(os.tmpdir(), `${uuid()}.bmp`)
+  fs.writeFileSync(tmpFilePath, rawBmp.data)
+  return tmpFilePath
+}
+
+type OutputType = 'stdout' | 'stderr'
+
+export class ProcessManager {
+  private label: string
+  private stdout = ''
+  private stderr = ''
+  private subprocess: ChildProcessWithoutNullStreams
+  private defaultOutput: OutputType
+  private onStdoutListener: (chunk: Uint8Array) => void
+  private onStderrListener: (chunk: Uint8Array) => void
+
+  constructor(
+    label: string,
+    subprocess: ChildProcessWithoutNullStreams,
+    defaultOutput: OutputType = 'stdout',
+    maxOutputSize = 1024 * 1024 * 10
+  ) {
+    this.label = label
+    this.defaultOutput = defaultOutput
+    this.subprocess = subprocess
+    const onDataListener = (outputType: OutputType) => (chunk: Uint8Array) => {
+      const chunkStr = Buffer.from(chunk).toString()
+      this[outputType] += chunkStr
+      if (this[outputType].length > maxOutputSize) {
+        this[outputType] = this[outputType].slice(-maxOutputSize)
+      }
+    }
+    this.onStdoutListener = onDataListener('stdout')
+    this.onStderrListener = onDataListener('stderr')
+
+    subprocess.stdout.on('data', this.onStdoutListener)
+    subprocess.stderr.on('data', this.onStderrListener)
+    nodeCleanup(() => {
+      console.log(this.recentOutput())
+      subprocess.kill()
+    })
+  }
+
+  private recentOutput() {
+    const length = parseInt(process.env.SUBPROCESSES_FINAL_LOG_LENGTH || '20')
+    return (
+      `\n\nLast STDOUT of ${this.label}:\n ${this.stdout.split('\n').slice(-length).join('\n')}\n\n` +
+      `Last STDERR of ${this.label}:\n ${this.stderr.split('\n').slice(-length).join('\n')}\n\n`
+    )
+  }
+
+  kill(): void {
+    this.subprocess.kill()
+  }
+
+  expectAlive(): void {
+    if (this.subprocess.exitCode !== null) {
+      throw new Error(`Process ${this.label} exited unexpectedly with code: ${this.subprocess.exitCode}`)
+    }
+  }
+
+  expectOutput(expected: string, outputType?: OutputType): void {
+    const outT = outputType || this.defaultOutput
+    if (!this[outT].includes(expected)) {
+      throw new Error(`Expected output: "${expected}" missing in ${this.label} process (${outputType})`)
+    }
+  }
+
+  async untilOutput(
+    expected: string,
+    outputType?: 'stderr' | 'stdout',
+    failOnExit = true,
+    timeoutMs = 120000,
+    waitMs = 1000
+  ): Promise<void> {
+    const start = Date.now()
+    while (true) {
+      try {
+        this.expectOutput(expected, outputType)
+        return
+      } catch (e) {
+        if (failOnExit) {
+          this.expectAlive()
+        }
+        if (Date.now() - start + waitMs >= timeoutMs) {
+          throw new Error(`untilOutput timeout reached. ${(e as Error).message}`)
+        }
+        await Utils.wait(waitMs)
+      }
+    }
+  }
+}

+ 4 - 1
tests/network-tests/src/flows/proposals/manageLeaderRole.ts

@@ -27,7 +27,10 @@ export default {
     return manageLeaderRole(api, env, WorkingGroups.StorageWorkingGroup, lock)
   },
   content: async function ({ api, env, lock }: FlowProps): Promise<void> {
-    return manageLeaderRole(api, env, WorkingGroups.ContentDirectoryWorkingGroup, lock)
+    return manageLeaderRole(api, env, WorkingGroups.ContentWorkingGroup, lock)
+  },
+  distribution: async function ({ api, env, lock }: FlowProps): Promise<void> {
+    return manageLeaderRole(api, env, WorkingGroups.DistributionWorkingGroup, lock)
   },
 }
 

+ 4 - 1
tests/network-tests/src/flows/proposals/workingGroupMintCapacityProposal.ts

@@ -17,7 +17,10 @@ export default {
   },
 
   content: async function ({ api, env, lock }: FlowProps): Promise<void> {
-    return workingGroupMintCapactiy(api, env, WorkingGroups.ContentDirectoryWorkingGroup, lock)
+    return workingGroupMintCapactiy(api, env, WorkingGroups.ContentWorkingGroup, lock)
+  },
+  distribution: async function ({ api, env, lock }: FlowProps): Promise<void> {
+    return workingGroupMintCapactiy(api, env, WorkingGroups.DistributionWorkingGroup, lock)
   },
 }
 

+ 0 - 45
tests/network-tests/src/flows/storageNode/getContentFromStorageNode.ts

@@ -1,45 +0,0 @@
-import axios from 'axios'
-import { assert } from 'chai'
-import { ContentId } from '@joystream/types/storage'
-import { registry } from '@joystream/types'
-
-import { FlowProps } from '../../Flow'
-import { Utils } from '../../utils'
-import { extendDebug } from '../../Debugger'
-
-export default async function getContentFromStorageNode({ api, query }: FlowProps): Promise<void> {
-  const debug = extendDebug('flow:getContentFromStorageNode')
-  debug('Started')
-
-  const videoTitle = 'Storage node test'
-
-  // Temporary solution (wait 2 minutes)
-  await Utils.wait(120000)
-
-  // Query video by title with where expression
-  const videoWhereQueryResult = await query.performWhereQueryByVideoTitle(videoTitle)
-
-  assert.equal(1, videoWhereQueryResult.data.videos.length, 'Should fetch only one video')
-
-  // Get dataObjectId from the queried video's media location
-  const dataObjectId = videoWhereQueryResult.data.videos[0].media.location.dataObjectId
-
-  assert(dataObjectId.length > 2, 'dataObjectId should not be empty')
-
-  const contentId = ContentId.decode(registry, dataObjectId)
-
-  // Decode data object
-  const dataObject = await api.getDataByContentId(contentId)
-
-  assert(dataObject, 'dataObject should not be null')
-
-  const response = await axios.get(`${process.env.STORAGE_NODE_URL}/${dataObjectId}`)
-
-  assert(response.headers['content-length'], 'Should have some value')
-
-  const contentLenght = Number.parseInt(response.headers['content-length'])
-
-  assert.equal(contentLenght, dataObject!.size_in_bytes.toJSON(), 'Content should be same size')
-
-  debug('Done')
-}

+ 92 - 0
tests/network-tests/src/flows/storagev2cli/createChannel.ts

@@ -0,0 +1,92 @@
+import { FlowProps } from '../../Flow'
+import { extendDebug } from '../../Debugger'
+import { WorkingGroups } from '../../Api'
+import { DistributorCLI } from '../../cli/distributor'
+import { JoystreamCLI } from '../../cli/joystream'
+import { StorageCLI } from '../../cli/storage'
+import { BuyMembershipHappyCaseFixture } from '../../fixtures/membershipModule'
+import { BN } from '@polkadot/util'
+import { FixtureRunner } from '../../Fixture'
+import { PublicApi as DistributorApi, Configuration as DistributorApiConfiguration } from '../../apis/distributorNode'
+import { assert } from 'chai'
+import { randomImgFile } from '../../cli/utils'
+
+export default async function createChannel({ api, env }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:createChannel')
+  debug('Started')
+
+  // Get working group leaders
+  const distributionLeaderId = await api.getLeadWorkerId(WorkingGroups.DistributionWorkingGroup)
+  const distributionLeader = await api.getGroupLead(WorkingGroups.DistributionWorkingGroup)
+  const storageLeaderId = await api.getLeadWorkerId(WorkingGroups.StorageWorkingGroup)
+  const storageLeader = await api.getGroupLead(WorkingGroups.StorageWorkingGroup)
+  if (!distributionLeaderId || !distributionLeader || !storageLeaderId || !storageLeader) {
+    throw new Error('Active storage and distributor leaders are required in this flow!')
+  }
+  const distributionLeaderSuri = api.getSuri(distributionLeader.role_account_id)
+  const storageLeaderSuri = api.getSuri(storageLeader.role_account_id)
+
+  // Create channel owner membership
+  const [channelOwnerKeypair] = await api.createKeyPairs(1)
+  const paidTermId = api.createPaidTermId(new BN(+(env.MEMBERSHIP_PAID_TERMS || 0)))
+  const buyMembershipFixture = new BuyMembershipHappyCaseFixture(api, [channelOwnerKeypair.address], paidTermId)
+  await new FixtureRunner(buyMembershipFixture).run()
+
+  // Send some funds to pay the deletion_prize
+  const channelOwnerBalance = new BN(100)
+  await api.treasuryTransferBalance(channelOwnerKeypair.address, channelOwnerBalance)
+
+  // Create CLI's
+  const distributorCli = new DistributorCLI([distributionLeaderSuri])
+  const joystreamCli = new JoystreamCLI()
+  const storageCli = new StorageCLI(storageLeaderSuri)
+
+  // Spawn storage node and distributor node servers
+  const storageServerProcess = await storageCli.spawnServer(storageLeaderId)
+  const distributorServerProcess = await distributorCli.spawnServer(distributionLeaderId)
+
+  // Import & select channel owner key in Joystream CLI
+  await joystreamCli.importKey(channelOwnerKeypair)
+  await joystreamCli.run('account:choose', ['--address', channelOwnerKeypair.address])
+
+  // Create channel
+  const { out: createChannelOut } = await joystreamCli.createChannel(
+    {
+      title: 'Test channel',
+      avatarPhotoPath: randomImgFile(300, 300),
+      coverPhotoPath: randomImgFile(1920, 500),
+      description: 'This is a test channel',
+      isPublic: true,
+      language: 'EN',
+      rewardAccount: channelOwnerKeypair.address,
+    },
+    ['--context', 'Member']
+  )
+
+  const assetIds = Array.from(createChannelOut.matchAll(/Uploading object ([0-9]+)/g)).map((res) => res[1])
+  if (!assetIds.length) {
+    throw new Error(`No uploaded asset ids found in createChannel output:\n${createChannelOut}`)
+  }
+
+  // TODO: Get asset ids etc. from query node
+
+  // Request asset from distributor node
+  const distributorApi = new DistributorApi(
+    new DistributorApiConfiguration({ basePath: 'http://localhost:3334/api/v1' })
+  )
+  await Promise.all(
+    assetIds.map(async (id) => {
+      const response = await distributorApi.publicAsset(id)
+      assert.equal(response.status, 200)
+    })
+  )
+
+  // Expect successful response & nodes to be alive
+  storageServerProcess.expectAlive()
+  distributorServerProcess.expectAlive()
+
+  storageServerProcess.kill()
+  distributorServerProcess.kill()
+
+  debug('Done')
+}

+ 54 - 0
tests/network-tests/src/flows/storagev2cli/initDistributionBucket.ts

@@ -0,0 +1,54 @@
+import { FlowProps } from '../../Flow'
+import { extendDebug } from '../../Debugger'
+import { WorkingGroups } from '../../Api'
+import { DistributorCLI } from '../../cli/distributor'
+
+export default async function initDistributionBucket({ api }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:initDistributionBucket')
+  debug('Started')
+
+  const leaderId = await api.getLeadWorkerId(WorkingGroups.DistributionWorkingGroup)
+  const leader = await api.getGroupLead(WorkingGroups.DistributionWorkingGroup)
+  if (!leaderId || !leader) {
+    throw new Error('Active distribution leader is required in this flow!')
+  }
+  const operatorId = leaderId.toString()
+  const leaderSuri = api.getSuri(leader.role_account_id)
+
+  const cli = new DistributorCLI([leaderSuri])
+
+  await cli.run('leader:set-buckets-per-bag-limit', ['--limit', '10'])
+  const { out: familyId } = await cli.run('leader:create-bucket-family')
+  const { out: bucketId } = await cli.run('leader:create-bucket', ['--familyId', familyId, '--acceptingBags', 'yes'])
+  await cli.run('leader:update-bag', ['--bagId', 'static:council', '--familyId', familyId, '--add', bucketId])
+  await cli.run('leader:update-dynamic-bag-policy', ['--type', 'Channel', '--policy', `${familyId}:1`])
+  await cli.run('leader:update-bucket-mode', ['--familyId', familyId, '--bucketId', bucketId, '--mode', 'on'])
+  await cli.run('leader:invite-bucket-operator', [
+    '--familyId',
+    familyId,
+    '--bucketId',
+    bucketId,
+    '--workerId',
+    operatorId,
+  ])
+  await cli.run('operator:accept-invitation', [
+    '--familyId',
+    familyId,
+    '--bucketId',
+    bucketId,
+    '--workerId',
+    operatorId,
+  ])
+  await cli.run('operator:set-metadata', [
+    '--familyId',
+    familyId,
+    '--bucketId',
+    bucketId,
+    '--workerId',
+    operatorId,
+    '--endpoint',
+    'http://localhost:3334',
+  ])
+
+  debug('Done')
+}

+ 44 - 0
tests/network-tests/src/flows/storagev2cli/initStorageBucket.ts

@@ -0,0 +1,44 @@
+import { FlowProps } from '../../Flow'
+import { extendDebug } from '../../Debugger'
+import { WorkingGroups } from '../../Api'
+import { StorageCLI } from '../../cli/storage'
+
+export default async function initStorageBucket({ api }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:initStorageBucket')
+  debug('Started')
+
+  const leaderId = await api.getLeadWorkerId(WorkingGroups.StorageWorkingGroup)
+  const leader = await api.getGroupLead(WorkingGroups.StorageWorkingGroup)
+  if (!leaderId || !leader) {
+    throw new Error('Active storage leader is required in this flow!')
+  }
+  const leaderSuri = api.getSuri(leader.role_account_id)
+
+  const operatorId = leaderId.toString()
+
+  const cli = new StorageCLI(leaderSuri)
+  await cli.run('leader:update-bag-limit', ['--limit', '10'])
+  await cli.run('leader:update-voucher-limits', ['--objects', '1000', '--size', '10000000000'])
+  const { out: bucketId } = await cli.run('leader:create-bucket', [
+    '--invited',
+    operatorId,
+    '--allow',
+    '--number',
+    '1000',
+    '--size',
+    '10000000000',
+  ])
+  await cli.run('operator:accept-invitation', ['--workerId', operatorId, '--bucketId', bucketId])
+  await cli.run('leader:update-bag', ['--add', bucketId, '--bagId', 'static:council'])
+  await cli.run('leader:update-dynamic-bag-policy', ['--bagType', 'Channel', '--number', '1'])
+  await cli.run('operator:set-metadata', [
+    '--bucketId',
+    bucketId,
+    '--operatorId',
+    operatorId,
+    '--endpoint',
+    'http://localhost:3333',
+  ])
+
+  debug('Done')
+}

+ 4 - 1
tests/network-tests/src/flows/workingGroup/leaderSetup.ts

@@ -13,7 +13,10 @@ export default {
     return leaderSetup(api, env, WorkingGroups.StorageWorkingGroup)
   },
   content: async function ({ api, env }: FlowProps): Promise<void> {
-    return leaderSetup(api, env, WorkingGroups.ContentDirectoryWorkingGroup)
+    return leaderSetup(api, env, WorkingGroups.ContentWorkingGroup)
+  },
+  distribution: async function ({ api, env }: FlowProps): Promise<void> {
+    return leaderSetup(api, env, WorkingGroups.DistributionWorkingGroup)
   },
 }
 

+ 4 - 1
tests/network-tests/src/flows/workingGroup/manageWorkerAsLead.ts

@@ -21,7 +21,10 @@ export default {
     return manageWorkerAsLead(api, env, WorkingGroups.StorageWorkingGroup)
   },
   content: async function ({ api, env }: FlowProps): Promise<void> {
-    return manageWorkerAsLead(api, env, WorkingGroups.ContentDirectoryWorkingGroup)
+    return manageWorkerAsLead(api, env, WorkingGroups.ContentWorkingGroup)
+  },
+  distribution: async function ({ api, env }: FlowProps): Promise<void> {
+    return manageWorkerAsLead(api, env, WorkingGroups.DistributionWorkingGroup)
   },
 }
 

+ 4 - 1
tests/network-tests/src/flows/workingGroup/manageWorkerAsWorker.ts

@@ -20,7 +20,10 @@ export default {
     return manageWorkerAsWorker(api, env, WorkingGroups.StorageWorkingGroup)
   },
   content: async function ({ api, env }: FlowProps): Promise<void> {
-    return manageWorkerAsWorker(api, env, WorkingGroups.ContentDirectoryWorkingGroup)
+    return manageWorkerAsWorker(api, env, WorkingGroups.ContentWorkingGroup)
+  },
+  distribution: async function ({ api, env }: FlowProps): Promise<void> {
+    return manageWorkerAsWorker(api, env, WorkingGroups.DistributionWorkingGroup)
   },
 }
 

+ 4 - 1
tests/network-tests/src/flows/workingGroup/workerPayout.ts

@@ -26,7 +26,10 @@ export default {
     return workerPayouts(api, env, WorkingGroups.StorageWorkingGroup, lock)
   },
   content: async function ({ api, env, lock }: FlowProps): Promise<void> {
-    return workerPayouts(api, env, WorkingGroups.ContentDirectoryWorkingGroup, lock)
+    return workerPayouts(api, env, WorkingGroups.ContentWorkingGroup, lock)
+  },
+  distribution: async function ({ api, env, lock }: FlowProps): Promise<void> {
+    return workerPayouts(api, env, WorkingGroups.DistributionWorkingGroup, lock)
   },
 }
 

+ 19 - 2
tests/network-tests/src/scenarios/full.ts

@@ -11,6 +11,9 @@ import atLeastValueBug from '../flows/workingGroup/atLeastValueBug'
 import manageWorkerAsLead from '../flows/workingGroup/manageWorkerAsLead'
 import manageWorkerAsWorker from '../flows/workingGroup/manageWorkerAsWorker'
 import workerPayout from '../flows/workingGroup/workerPayout'
+import initDistributionBucket from '../flows/storagev2cli/initDistributionBucket'
+import initStorageBucket from '../flows/storagev2cli/initStorageBucket'
+import createChannel from '../flows/storagev2cli/createChannel'
 import { scenario } from '../Scenario'
 
 scenario(async ({ job }) => {
@@ -25,22 +28,36 @@ scenario(async ({ job }) => {
     validatorCountProposal,
     wgMintCapacityProposal.storage,
     wgMintCapacityProposal.content,
+    wgMintCapacityProposal.distribution,
     manageLeaderRole.storage,
     manageLeaderRole.content,
+    manageLeaderRole.distribution,
   ]).requires(councilJob)
 
-  const leadSetupJob = job('setup leads', [leaderSetup.storage, leaderSetup.content]).after(proposalsJob)
+  const leadSetupJob = job('setup leads', [leaderSetup.storage, leaderSetup.content, leaderSetup.distribution]).after(
+    proposalsJob
+  )
 
   // Test bug only on one instance of working group is sufficient
   job('at least value bug', atLeastValueBug).requires(leadSetupJob)
 
   // tests minting payouts (requires council to set mint capacity)
-  job('worker payouts', [workerPayout.storage, workerPayout.content]).requires(leadSetupJob).requires(councilJob)
+  job('worker payouts', [workerPayout.storage, workerPayout.content, workerPayout.distribution])
+    .requires(leadSetupJob)
+    .requires(councilJob)
 
   job('working group tests', [
     manageWorkerAsLead.storage,
     manageWorkerAsWorker.storage,
     manageWorkerAsLead.content,
     manageWorkerAsWorker.content,
+    manageWorkerAsLead.distribution,
+    manageWorkerAsWorker.distribution,
   ]).requires(leadSetupJob)
+
+  const initBuckets = job('init storage and distribution buckets', [
+    initDistributionBucket,
+    initStorageBucket,
+  ]).requires(leadSetupJob)
+  job('create channel', createChannel).requires(initBuckets)
 })

+ 1 - 1
tests/network-tests/src/sender.ts

@@ -17,7 +17,7 @@ export enum LogLevel {
 
 export class Sender {
   private readonly api: ApiPromise
-  private static readonly asyncLock: AsyncLock = new AsyncLock()
+  static readonly asyncLock: AsyncLock = new AsyncLock()
   private readonly keyring: Keyring
   private readonly debug: Debugger.Debugger
   private logs: LogLevel = LogLevel.None

+ 12 - 0
yarn.lock

@@ -5464,6 +5464,13 @@
   resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652"
   integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==
 
+"@types/bmp-js@^0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@types/bmp-js/-/bmp-js-0.1.0.tgz#301afe2bb3ac7ef0f18465966e4166f0491b3332"
+  integrity sha512-uMU85ROcmlY1f4mVPTlNodRXa6Z5f0AIxvv5b0pvjty3KNg7ljf5lNSspHgaF6iFDCiGpLQmJna+VwEpUC9TyA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6":
   version "4.11.6"
   resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
@@ -9227,6 +9234,11 @@ bluebird@~3.4.1:
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
   integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=
 
+bmp-js@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
+  integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM=
+
 bn.js@4.12.0, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.12.0, bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.1.3, bn.js@^5.2.0:
   version "4.12.0"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"