Bläddra i källkod

Refactor & update the tests

Leszek Wiesner 3 år sedan
förälder
incheckning
d7df8c97e2
34 ändrade filer med 622 tillägg och 298 borttagningar
  1. 1 1
      .github/workflows/run-network-tests.yml
  2. 1 0
      cli/src/commands/content/createChannel.ts
  3. 1 1
      start.sh
  4. 1 1
      tests/network-tests/package.json
  5. 45 0
      tests/network-tests/run-full-tests.sh
  6. 25 5
      tests/network-tests/src/Api.ts
  7. 12 0
      tests/network-tests/src/QueryNodeApi.ts
  8. 26 10
      tests/network-tests/src/Scenario.ts
  9. 23 23
      tests/network-tests/src/apis/distributorNode/api.ts
  10. 2 2
      tests/network-tests/src/apis/distributorNode/base.ts
  11. 2 2
      tests/network-tests/src/apis/distributorNode/common.ts
  12. 2 2
      tests/network-tests/src/apis/distributorNode/configuration.ts
  13. 2 2
      tests/network-tests/src/apis/distributorNode/index.ts
  14. 33 35
      tests/network-tests/src/apis/storageNode/api.ts
  15. 24 3
      tests/network-tests/src/cli/base.ts
  16. 11 1
      tests/network-tests/src/cli/distributor.ts
  17. 24 6
      tests/network-tests/src/cli/joystream.ts
  18. 12 1
      tests/network-tests/src/cli/storage.ts
  19. 28 13
      tests/network-tests/src/cli/utils.ts
  20. 71 0
      tests/network-tests/src/flows/clis/createChannel.ts
  21. 0 0
      tests/network-tests/src/flows/clis/initDistributionBucket.ts
  22. 0 0
      tests/network-tests/src/flows/clis/initStorageBucket.ts
  23. 4 4
      tests/network-tests/src/flows/storagev2/initStorage.ts
  24. 0 92
      tests/network-tests/src/flows/storagev2cli/createChannel.ts
  25. 117 6
      tests/network-tests/src/graphql/generated/queries.ts
  26. 57 1
      tests/network-tests/src/graphql/queries/storagev2.graphql
  27. 45 0
      tests/network-tests/src/scenarios/combined.ts
  28. 0 66
      tests/network-tests/src/scenarios/full.ts
  29. 16 0
      tests/network-tests/src/scenarios/init-storage-and-distribution.ts
  30. 0 16
      tests/network-tests/src/scenarios/initStorageV2.ts
  31. 28 0
      tests/network-tests/src/scenarios/proposals.ts
  32. 2 4
      tests/network-tests/src/scenarios/setup-new-chain.ts
  33. 1 1
      tests/network-tests/src/sender.ts
  34. 6 0
      tests/network-tests/src/utils.ts

+ 1 - 1
.github/workflows/run-network-tests.yml

@@ -140,7 +140,7 @@ jobs:
       - name: Ensure tests are runnable
         run: yarn workspace network-tests build
       - name: Execute network tests
-        run: tests/network-tests/run-tests.sh full
+        run: tests/network-tests/run-full-tests.sh
 
   new_chain_setup:
     name: Initialize new chain

+ 1 - 0
cli/src/commands/content/createChannel.ts

@@ -49,6 +49,7 @@ export default class CreateChannelCommand extends UploadCommandBase {
     const channelCreationParameters = createTypeFromConstructor(ChannelCreationParameters, {
       assets,
       meta: metadataToBytes(ChannelMetadata, meta),
+      reward_account: channelInput.rewardAccount,
     })
 
     this.jsonPrettyPrint(JSON.stringify({ assets: assets?.toJSON(), metadata: meta }))

+ 1 - 1
start.sh

@@ -27,7 +27,7 @@ docker-compose up -d joystream-node
 ## Init the chain with some state
 export SKIP_MOCK_CONTENT=true
 HOST_IP=$(tests/network-tests/get-host-ip.sh)
-export STORAGE_1_URL="http://${HOST_IP}:3333"
+export COLOSSUS_1_URL="http://${HOST_IP}:3333"
 export DISTRIBUTOR_1_URL="http://${HOST_IP}:3334"
 ./tests/network-tests/run-test-scenario.sh ${INIT_CHAIN_SCENARIO}
 

+ 1 - 1
tests/network-tests/package.json

@@ -11,7 +11,7 @@
     "checks": "tsc --noEmit --pretty && prettier ./ --check && yarn lint",
     "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:api:distributor-node": "yarn openapi-generator-cli generate -i ../../distributor-node/src/api-spec/public.yml -g typescript-axios -o ./src/apis/distributorNode",
     "generate:api:all": "yarn generate:api:storage-node && yarn generate:api:distributor-node && yarn format",
     "generate:types:graphql": "graphql-codegen",
     "generate:all": "yarn generate:types:graphql && yarn generate:api:all"

+ 45 - 0
tests/network-tests/run-full-tests.sh

@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+set -e
+
+SCRIPT_PATH="$(dirname "${BASH_SOURCE[0]}")"
+cd $SCRIPT_PATH
+
+CONTAINER_ID=$(./run-test-node-docker.sh)
+
+# function cleanup() {
+#     docker logs ${CONTAINER_ID} --tail 15
+#     docker-compose -f ../../docker-compose.yml down -v
+# }
+
+# trap cleanup EXIT
+
+sleep 3
+
+# Display runtime version
+yarn workspace api-scripts tsnode-strict src/status.ts | grep Runtime
+
+# Start any other services we want
+# docker-compose -f ../../docker-compose.yml up -d colossus-1
+
+# Start a query-node
+../../query-node/start.sh
+
+# Run proposals tests first, since they require no leads hired
+./run-test-scenario.sh proposals
+
+# Setup storage & distribution
+HOST_IP=$(./get-host-ip.sh)
+export COLOSSUS_1_URL="http://${HOST_IP}:3333"
+export COLOSSUS_1_WORKER_ID=1
+export COLOSSUS_1_ACCOUNT_URI=//testing//worker//Storage//${COLOSSUS_1_WORKER_ID}
+export DISTRIBUTOR_1_URL="http://${HOST_IP}:3334"
+export DISTRIBUTOR_1_WORKER_ID=1
+export DISTRIBUTOR_1_ACCOUNT_URI=//testing//worker//Distribution//${DISTRIBUTOR_1_WORKER_ID}
+REUSE_KEYS=true ./run-test-scenario.sh init-storage-and-distribution
+
+# Start colossus & argus
+docker-compose -f ../../docker-compose.yml up -d colossus-1
+docker-compose -f ../../docker-compose.yml up -d distributor-1
+
+# Run combined tests reusing the existing keys
+REUSE_KEYS=true ./run-test-scenario.sh combined

+ 25 - 5
tests/network-tests/src/Api.ts

@@ -55,11 +55,19 @@ type EventType<
   Method extends EventMethod<Section>
 > = ApiPromise['events'][Section][Method] extends AugmentedEvent<'promise', infer T> ? IEvent<T> : never
 
+export type KeyGenInfo = {
+  start: number
+  final: number
+  custom: string[]
+}
+
 export class ApiFactory {
   private readonly api: ApiPromise
   private readonly keyring: Keyring
   // number used as part of key derivation path
   private keyId = 0
+  // stores names of the created custom keys
+  private customKeys: string[] = []
   // mapping from account address to key id.
   // To be able to re-derive keypair externally when mini-secret is known.
   readonly addressesToKeyId: Map<string, number> = new Map()
@@ -122,26 +130,34 @@ export class ApiFactory {
     const keys: { key: KeyringPair; id: number }[] = []
     for (let i = 0; i < n; i++) {
       const id = this.keyId++
-      const key = this.createCustomKeyPair(`${id}`)
+      const key = this.createKeyPair(`${id}`)
       keys.push({ key, id })
       this.addressesToKeyId.set(key.address, id)
     }
     return keys
   }
 
-  public createCustomKeyPair(customPath: string): KeyringPair {
-    const uri = `${this.miniSecret}//testing//${customPath}`
+  private createKeyPair(suriPath: string, isCustom = false): KeyringPair {
+    if (isCustom) {
+      this.customKeys.push(suriPath)
+    }
+    const uri = `${this.miniSecret}//testing//${suriPath}`
     const pair = this.keyring.addFromUri(uri)
     this.addressesToSuri.set(pair.address, uri)
     return pair
   }
 
-  public keyGenInfo(): { start: number; final: number } {
+  public createCustomKeyPair(customPath: string): KeyringPair {
+    return this.createKeyPair(customPath, true)
+  }
+
+  public keyGenInfo(): KeyGenInfo {
     const start = 0
     const final = this.keyId
     return {
       start,
       final,
+      custom: this.customKeys,
     }
   }
 
@@ -180,6 +196,10 @@ export class Api {
     return this.api.query
   }
 
+  public get consts(): ApiPromise['consts'] {
+    return this.api.consts
+  }
+
   public get tx(): ApiPromise['tx'] {
     return this.api.tx
   }
@@ -226,7 +246,7 @@ export class Api {
     return this.factory.createCustomKeyPair(path)
   }
 
-  public keyGenInfo(): { start: number; final: number } {
+  public keyGenInfo(): KeyGenInfo {
     return this.factory.keyGenInfo()
   }
 

+ 12 - 0
tests/network-tests/src/QueryNodeApi.ts

@@ -5,6 +5,10 @@ import {
   GetDataObjectsByIdsQuery,
   GetDataObjectsByIdsQueryVariables,
   GetDataObjectsByIds,
+  ChannelFieldsFragment,
+  GetChannelById,
+  GetChannelByIdQuery,
+  GetChannelByIdQueryVariables,
 } from './graphql/generated/queries'
 import { Maybe } from './graphql/generated/schema'
 import { OperationDefinitionNode } from 'graphql'
@@ -100,6 +104,14 @@ export class QueryNodeApi {
     return (await this.queryNodeProvider.query<QueryT, VariablesT>({ query, variables })).data[resultKey]
   }
 
+  public async channelById(id: string): Promise<Maybe<ChannelFieldsFragment>> {
+    return this.uniqueEntityQuery<GetChannelByIdQuery, GetChannelByIdQueryVariables>(
+      GetChannelById,
+      { id },
+      'channelByUniqueInput'
+    )
+  }
+
   public async getDataObjectsByIds(ids: string[]): Promise<StorageDataObjectFieldsFragment[]> {
     return this.multipleEntitiesQuery<GetDataObjectsByIdsQuery, GetDataObjectsByIdsQueryVariables>(
       GetDataObjectsByIds,

+ 26 - 10
tests/network-tests/src/Scenario.ts

@@ -1,5 +1,5 @@
 import { WsProvider } from '@polkadot/api'
-import { ApiFactory, Api } from './Api'
+import { ApiFactory, Api, KeyGenInfo } from './Api'
 import { QueryNodeApi } from './QueryNodeApi'
 import { config } from 'dotenv'
 import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'
@@ -9,7 +9,7 @@ import { Job } from './Job'
 import { JobManager } from './JobManager'
 import { ResourceManager } from './Resources'
 import fetch from 'cross-fetch'
-import fs from 'fs'
+import fs, { readFileSync } from 'fs'
 
 export type ScenarioProps = {
   env: NodeJS.ProcessEnv
@@ -17,22 +17,29 @@ export type ScenarioProps = {
   job: (label: string, flows: Flow[] | Flow) => Job
 }
 
+const OUTPUT_FILE_PATH = 'output.json'
+
+type TestsOutput = {
+  accounts: { [k: string]: number }
+  keyIds: KeyGenInfo
+  miniSecret: string
+}
+
 function writeOutput(api: Api, miniSecret: string) {
-  const outputFilename = 'output.json'
-  console.error('Writing generated account to', outputFilename)
+  console.error('Writing generated account to', OUTPUT_FILE_PATH)
   // account to key ids
   const accounts = api.getAllGeneratedAccounts()
 
   // first and last key id used to generate keys in this scenario
   const keyIds = api.keyGenInfo()
 
-  const output = {
+  const output: TestsOutput = {
     accounts,
     keyIds,
     miniSecret,
   }
 
-  fs.writeFileSync(outputFilename, JSON.stringify(output, undefined, 2))
+  fs.writeFileSync(OUTPUT_FILE_PATH, JSON.stringify(output, undefined, 2))
 }
 
 export async function scenario(scene: (props: ScenarioProps) => Promise<void>): Promise<void> {
@@ -53,12 +60,21 @@ export async function scenario(scene: (props: ScenarioProps) => Promise<void>):
 
   const api = apiFactory.getApi('Key Generation')
 
-  // Generate all key ids before START_KEY_ID
-  const startKeyId = parseInt(env.START_KEY_ID || '0')
-  if (startKeyId) {
-    api.createKeyPairs(startKeyId)
+  // Generate all key ids based on REUSE_KEYS or START_KEY_ID (if provided)
+  const reuseKeys = Boolean(env.REUSE_KEYS)
+  let startKeyId: number
+  let customKeys: string[] = []
+  if (reuseKeys) {
+    const output = JSON.parse(readFileSync(OUTPUT_FILE_PATH).toString()) as TestsOutput
+    startKeyId = output.keyIds.final
+    customKeys = output.keyIds.custom
+  } else {
+    startKeyId = parseInt(env.START_KEY_ID || '0')
   }
 
+  api.createKeyPairs(startKeyId)
+  customKeys.forEach((k) => api.createCustomKeyPair(k))
+
   const queryNodeUrl: string = env.QUERY_NODE_URL || 'http://127.0.0.1:8081/graphql'
 
   const queryNodeProvider = new ApolloClient({

+ 23 - 23
tests/network-tests/src/apis/distributorNode/api.ts

@@ -1,8 +1,8 @@
 /* tslint:disable */
 /* eslint-disable */
 /**
- * Distributor node API
- * Distributor node API
+ * Distributor node public API
+ * Distributor node public API
  *
  * The version of the OpenAPI document: 0.1.0
  * Contact: info@joystream.org
@@ -127,10 +127,10 @@ export interface StatusResponse {
 }
 
 /**
- * PublicApi - axios parameter creator
+ * DefaultApi - axios parameter creator
  * @export
  */
-export const PublicApiAxiosParamCreator = function (configuration?: Configuration) {
+export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
   return {
     /**
      * Returns a media file.
@@ -141,7 +141,7 @@ export const PublicApiAxiosParamCreator = function (configuration?: Configuratio
     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)))
+      const localVarPath = `/assets/{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
@@ -171,7 +171,7 @@ export const PublicApiAxiosParamCreator = function (configuration?: Configuratio
     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)))
+      const localVarPath = `/assets/{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
@@ -250,11 +250,11 @@ export const PublicApiAxiosParamCreator = function (configuration?: Configuratio
 }
 
 /**
- * PublicApi - functional programming interface
+ * DefaultApi - functional programming interface
  * @export
  */
-export const PublicApiFp = function (configuration?: Configuration) {
-  const localVarAxiosParamCreator = PublicApiAxiosParamCreator(configuration)
+export const DefaultApiFp = function (configuration?: Configuration) {
+  const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
   return {
     /**
      * Returns a media file.
@@ -308,11 +308,11 @@ export const PublicApiFp = function (configuration?: Configuration) {
 }
 
 /**
- * PublicApi - factory interface
+ * DefaultApi - factory interface
  * @export
  */
-export const PublicApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
-  const localVarFp = PublicApiFp(configuration)
+export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
+  const localVarFp = DefaultApiFp(configuration)
   return {
     /**
      * Returns a media file.
@@ -352,21 +352,21 @@ export const PublicApiFactory = function (configuration?: Configuration, basePat
 }
 
 /**
- * PublicApi - object-oriented interface
+ * DefaultApi - object-oriented interface
  * @export
- * @class PublicApi
+ * @class DefaultApi
  * @extends {BaseAPI}
  */
-export class PublicApi extends BaseAPI {
+export class DefaultApi extends BaseAPI {
   /**
    * Returns a media file.
    * @param {string} objectId Data Object ID
    * @param {*} [options] Override http request option.
    * @throws {RequiredError}
-   * @memberof PublicApi
+   * @memberof DefaultApi
    */
   public publicAsset(objectId: string, options?: any) {
-    return PublicApiFp(this.configuration)
+    return DefaultApiFp(this.configuration)
       .publicAsset(objectId, options)
       .then((request) => request(this.axios, this.basePath))
   }
@@ -376,10 +376,10 @@ export class PublicApi extends BaseAPI {
    * @param {string} objectId Data Object ID
    * @param {*} [options] Override http request option.
    * @throws {RequiredError}
-   * @memberof PublicApi
+   * @memberof DefaultApi
    */
   public publicAssetHead(objectId: string, options?: any) {
-    return PublicApiFp(this.configuration)
+    return DefaultApiFp(this.configuration)
       .publicAssetHead(objectId, options)
       .then((request) => request(this.axios, this.basePath))
   }
@@ -388,10 +388,10 @@ export class PublicApi extends BaseAPI {
    * Returns list of distributed buckets
    * @param {*} [options] Override http request option.
    * @throws {RequiredError}
-   * @memberof PublicApi
+   * @memberof DefaultApi
    */
   public publicBuckets(options?: any) {
-    return PublicApiFp(this.configuration)
+    return DefaultApiFp(this.configuration)
       .publicBuckets(options)
       .then((request) => request(this.axios, this.basePath))
   }
@@ -400,10 +400,10 @@ export class PublicApi extends BaseAPI {
    * Returns json object describing current node status.
    * @param {*} [options] Override http request option.
    * @throws {RequiredError}
-   * @memberof PublicApi
+   * @memberof DefaultApi
    */
   public publicStatus(options?: any) {
-    return PublicApiFp(this.configuration)
+    return DefaultApiFp(this.configuration)
       .publicStatus(options)
       .then((request) => request(this.axios, this.basePath))
   }

+ 2 - 2
tests/network-tests/src/apis/distributorNode/base.ts

@@ -1,8 +1,8 @@
 /* tslint:disable */
 /* eslint-disable */
 /**
- * Distributor node API
- * Distributor node API
+ * Distributor node public API
+ * Distributor node public API
  *
  * The version of the OpenAPI document: 0.1.0
  * Contact: info@joystream.org

+ 2 - 2
tests/network-tests/src/apis/distributorNode/common.ts

@@ -1,8 +1,8 @@
 /* tslint:disable */
 /* eslint-disable */
 /**
- * Distributor node API
- * Distributor node API
+ * Distributor node public API
+ * Distributor node public API
  *
  * The version of the OpenAPI document: 0.1.0
  * Contact: info@joystream.org

+ 2 - 2
tests/network-tests/src/apis/distributorNode/configuration.ts

@@ -1,8 +1,8 @@
 /* tslint:disable */
 /* eslint-disable */
 /**
- * Distributor node API
- * Distributor node API
+ * Distributor node public API
+ * Distributor node public API
  *
  * The version of the OpenAPI document: 0.1.0
  * Contact: info@joystream.org

+ 2 - 2
tests/network-tests/src/apis/distributorNode/index.ts

@@ -1,8 +1,8 @@
 /* tslint:disable */
 /* eslint-disable */
 /**
- * Distributor node API
- * Distributor node API
+ * Distributor node public API
+ * Distributor node public API
  *
  * The version of the OpenAPI document: 0.1.0
  * Contact: info@joystream.org

+ 33 - 35
tests/network-tests/src/apis/storageNode/api.ts

@@ -195,7 +195,7 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    publicApiAuthTokenForUploading: async (tokenRequest?: TokenRequest, options: any = {}): Promise<RequestArgs> => {
+    filesApiAuthTokenForUploading: 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)
@@ -226,9 +226,9 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    publicApiGetFile: async (id: string, options: any = {}): Promise<RequestArgs> => {
+    filesApiGetFile: async (id: string, options: any = {}): Promise<RequestArgs> => {
       // verify required parameter 'id' is not null or undefined
-      assertParamExists('publicApiGetFile', 'id', id)
+      assertParamExists('filesApiGetFile', '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)
@@ -256,9 +256,9 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    publicApiGetFileHeaders: async (id: string, options: any = {}): Promise<RequestArgs> => {
+    filesApiGetFileHeaders: async (id: string, options: any = {}): Promise<RequestArgs> => {
       // verify required parameter 'id' is not null or undefined
-      assertParamExists('publicApiGetFileHeaders', 'id', id)
+      assertParamExists('filesApiGetFileHeaders', '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)
@@ -289,7 +289,7 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    publicApiUploadFile: async (
+    filesApiUploadFile: async (
       dataObjectId: string,
       storageBucketId: string,
       bagId: string,
@@ -297,11 +297,11 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
       options: any = {}
     ): Promise<RequestArgs> => {
       // verify required parameter 'dataObjectId' is not null or undefined
-      assertParamExists('publicApiUploadFile', 'dataObjectId', dataObjectId)
+      assertParamExists('filesApiUploadFile', 'dataObjectId', dataObjectId)
       // verify required parameter 'storageBucketId' is not null or undefined
-      assertParamExists('publicApiUploadFile', 'storageBucketId', storageBucketId)
+      assertParamExists('filesApiUploadFile', 'storageBucketId', storageBucketId)
       // verify required parameter 'bagId' is not null or undefined
-      assertParamExists('publicApiUploadFile', 'bagId', bagId)
+      assertParamExists('filesApiUploadFile', '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)
@@ -362,11 +362,11 @@ export const FilesApiFp = function (configuration?: Configuration) {
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    async publicApiAuthTokenForUploading(
+    async filesApiAuthTokenForUploading(
       tokenRequest?: TokenRequest,
       options?: any
     ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2011>> {
-      const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiAuthTokenForUploading(tokenRequest, options)
+      const localVarAxiosArgs = await localVarAxiosParamCreator.filesApiAuthTokenForUploading(tokenRequest, options)
       return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
     },
     /**
@@ -375,11 +375,11 @@ export const FilesApiFp = function (configuration?: Configuration) {
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    async publicApiGetFile(
+    async filesApiGetFile(
       id: string,
       options?: any
     ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
-      const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiGetFile(id, options)
+      const localVarAxiosArgs = await localVarAxiosParamCreator.filesApiGetFile(id, options)
       return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
     },
     /**
@@ -388,11 +388,11 @@ export const FilesApiFp = function (configuration?: Configuration) {
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    async publicApiGetFileHeaders(
+    async filesApiGetFileHeaders(
       id: string,
       options?: any
     ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
-      const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiGetFileHeaders(id, options)
+      const localVarAxiosArgs = await localVarAxiosParamCreator.filesApiGetFileHeaders(id, options)
       return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)
     },
     /**
@@ -404,14 +404,14 @@ export const FilesApiFp = function (configuration?: Configuration) {
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    async publicApiUploadFile(
+    async filesApiUploadFile(
       dataObjectId: string,
       storageBucketId: string,
       bagId: string,
       file?: any,
       options?: any
     ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse201>> {
-      const localVarAxiosArgs = await localVarAxiosParamCreator.publicApiUploadFile(
+      const localVarAxiosArgs = await localVarAxiosParamCreator.filesApiUploadFile(
         dataObjectId,
         storageBucketId,
         bagId,
@@ -436,10 +436,8 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
      * @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))
+    filesApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any): AxiosPromise<InlineResponse2011> {
+      return localVarFp.filesApiAuthTokenForUploading(tokenRequest, options).then((request) => request(axios, basePath))
     },
     /**
      * Returns a media file.
@@ -447,8 +445,8 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
      * @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))
+    filesApiGetFile(id: string, options?: any): AxiosPromise<any> {
+      return localVarFp.filesApiGetFile(id, options).then((request) => request(axios, basePath))
     },
     /**
      * Returns a media file headers.
@@ -456,8 +454,8 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
      * @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))
+    filesApiGetFileHeaders(id: string, options?: any): AxiosPromise<void> {
+      return localVarFp.filesApiGetFileHeaders(id, options).then((request) => request(axios, basePath))
     },
     /**
      * Upload data
@@ -468,7 +466,7 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      */
-    publicApiUploadFile(
+    filesApiUploadFile(
       dataObjectId: string,
       storageBucketId: string,
       bagId: string,
@@ -476,7 +474,7 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
       options?: any
     ): AxiosPromise<InlineResponse201> {
       return localVarFp
-        .publicApiUploadFile(dataObjectId, storageBucketId, bagId, file, options)
+        .filesApiUploadFile(dataObjectId, storageBucketId, bagId, file, options)
         .then((request) => request(axios, basePath))
     },
   }
@@ -496,9 +494,9 @@ export class FilesApi extends BaseAPI {
    * @throws {RequiredError}
    * @memberof FilesApi
    */
-  public publicApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any) {
+  public filesApiAuthTokenForUploading(tokenRequest?: TokenRequest, options?: any) {
     return FilesApiFp(this.configuration)
-      .publicApiAuthTokenForUploading(tokenRequest, options)
+      .filesApiAuthTokenForUploading(tokenRequest, options)
       .then((request) => request(this.axios, this.basePath))
   }
 
@@ -509,9 +507,9 @@ export class FilesApi extends BaseAPI {
    * @throws {RequiredError}
    * @memberof FilesApi
    */
-  public publicApiGetFile(id: string, options?: any) {
+  public filesApiGetFile(id: string, options?: any) {
     return FilesApiFp(this.configuration)
-      .publicApiGetFile(id, options)
+      .filesApiGetFile(id, options)
       .then((request) => request(this.axios, this.basePath))
   }
 
@@ -522,9 +520,9 @@ export class FilesApi extends BaseAPI {
    * @throws {RequiredError}
    * @memberof FilesApi
    */
-  public publicApiGetFileHeaders(id: string, options?: any) {
+  public filesApiGetFileHeaders(id: string, options?: any) {
     return FilesApiFp(this.configuration)
-      .publicApiGetFileHeaders(id, options)
+      .filesApiGetFileHeaders(id, options)
       .then((request) => request(this.axios, this.basePath))
   }
 
@@ -538,9 +536,9 @@ export class FilesApi extends BaseAPI {
    * @throws {RequiredError}
    * @memberof FilesApi
    */
-  public publicApiUploadFile(dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options?: any) {
+  public filesApiUploadFile(dataObjectId: string, storageBucketId: string, bagId: string, file?: any, options?: any) {
     return FilesApiFp(this.configuration)
-      .publicApiUploadFile(dataObjectId, storageBucketId, bagId, file, options)
+      .filesApiUploadFile(dataObjectId, storageBucketId, bagId, file, options)
       .then((request) => request(this.axios, this.basePath))
   }
 }

+ 24 - 3
tests/network-tests/src/cli/base.ts

@@ -17,16 +17,37 @@ export abstract class CLI {
     this.env = {
       ...process.env,
       AUTO_CONFIRM: 'true',
+      FORCE_COLOR: '0',
       ...defaultEnv,
     }
     this.defaultArgs = [...defaultArgs]
   }
 
-  async run(command: string, args: string[] = []): Promise<CommandResult> {
+  protected getArgs(customArgs: string[]): string[] {
+    return [...this.defaultArgs, ...customArgs]
+  }
+
+  protected getFlagStringValue(args: string[], flag: string, alias?: string): string | undefined {
+    const flagIndex = args.lastIndexOf(flag)
+    const aliasIndex = alias ? args.lastIndexOf(alias) : -1
+    const flagOrAliasIndex = Math.max(flagIndex, aliasIndex)
+    if (flagOrAliasIndex === -1) {
+      return undefined
+    }
+    const nextArg = args[flagOrAliasIndex + 1]
+    return nextArg
+  }
+
+  async run(command: string, customArgs: string[] = [], lockKeys: 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 })
+    const { stdout, stderr } = await Sender.asyncLock.acquire(
+      lockKeys.map((k) => `nonce-${k}`),
+      () =>
+        pExecFile(this.binPath, [command, ...this.getArgs(customArgs)], {
+          env,
+          cwd: this.rootPath,
+        })
     )
     return { stdout, stderr, out: stdout.trim() }
   }

+ 11 - 1
tests/network-tests/src/cli/distributor.ts

@@ -1,13 +1,16 @@
 import path from 'path'
 import { spawn } from 'child_process'
 import { DistributorNodeConfiguration } from '@joystream/distributor-cli/src/types/generated/ConfigJson'
-import { CLI } from './base'
+import { CLI, CommandResult } from './base'
 import { WorkerId } from '@joystream/types/working-group'
 import { ProcessManager } from './utils'
+import Keyring from '@polkadot/keyring'
 
 const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../distributor-node')
 
 export class DistributorCLI extends CLI {
+  protected keys: string[]
+
   constructor(keyUris: string[]) {
     const keys: DistributorNodeConfiguration['keys'] = keyUris.map((suri) => ({
       suri,
@@ -16,6 +19,13 @@ export class DistributorCLI extends CLI {
       JOYSTREAM_DISTRIBUTOR__KEYS: JSON.stringify(keys),
     }
     super(CLI_ROOT_PATH, defaultEnv)
+    const keyring = new Keyring({ type: 'sr25519' })
+    keyUris.forEach((uri) => keyring.addFromUri(uri))
+    this.keys = keyring.getPairs().map((p) => p.address)
+  }
+
+  async run(command: string, customArgs: string[] = [], keyLocks?: string[]): Promise<CommandResult> {
+    return super.run(command, customArgs, keyLocks || this.keys)
   }
 
   async spawnServer(

+ 24 - 6
tests/network-tests/src/cli/joystream.ts

@@ -1,23 +1,41 @@
 import { KeyringPair } from '@polkadot/keyring/types'
 import path from 'path'
 import { CLI, CommandResult } from './base'
-import { tmpJsonFile } from './utils'
+import { TmpFileManager } from './utils'
 import { ChannelInputParameters } from '@joystream/cli/src/Types'
 
 const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../cli')
 
 export class JoystreamCLI extends CLI {
-  constructor() {
+  protected keys: string[] = []
+  protected chosenKey: string | undefined
+  protected tmpFileManager: TmpFileManager
+
+  constructor(tmpFileManager: TmpFileManager) {
     super(CLI_ROOT_PATH)
+    this.tmpFileManager = tmpFileManager
+  }
+
+  async importKey(pair: KeyringPair): Promise<void> {
+    const jsonFile = this.tmpFileManager.jsonFile(pair.toJson())
+    await this.run('account:import', [jsonFile])
+    this.keys.push(pair.address)
+  }
+
+  async chooseKey(address: string): Promise<void> {
+    if (!this.keys.includes(address)) {
+      throw new Error('Cannot choose a key that was not imported via JoystreamCLI.importKey!')
+    }
+    await this.run('account:choose', ['--address', address])
+    this.chosenKey = address
   }
 
-  async importKey(pair: KeyringPair): Promise<CommandResult> {
-    const jsonFile = tmpJsonFile(pair.toJson())
-    return this.run('account:import', [jsonFile])
+  async run(command: string, customArgs: string[] = [], keyLocks?: string[]): Promise<CommandResult> {
+    return super.run(command, customArgs, keyLocks || (this.chosenKey ? [this.chosenKey] : []))
   }
 
   async createChannel(inputData: ChannelInputParameters, args: string[]): Promise<CommandResult> {
-    const jsonFile = tmpJsonFile(inputData)
+    const jsonFile = this.tmpFileManager.jsonFile(inputData)
     return this.run('content:createChannel', ['--input', jsonFile, ...args])
   }
 }

+ 12 - 1
tests/network-tests/src/cli/storage.ts

@@ -1,11 +1,12 @@
 import path from 'path'
-import { CLI } from './base'
+import { CLI, CommandResult } 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'
+import { Keyring } from '@polkadot/keyring'
 
 const CLI_ROOT_PATH = path.resolve(__dirname, '../../../../storage-node-v2')
 
@@ -18,6 +19,16 @@ export class StorageCLI extends CLI {
     this.defaultArgs = ['--accountUri', defaultSuri]
   }
 
+  async run(command: string, customArgs: string[] = []): Promise<CommandResult> {
+    const args = this.getArgs(customArgs)
+    const accountUri = this.getFlagStringValue(args, '--accountUri', '-y')
+    if (!accountUri) {
+      throw new Error('Missing accountUri')
+    }
+    const accountKey = new Keyring({ type: 'sr25519' }).createFromUri(accountUri).address
+    return super.run(command, args, [accountKey])
+  }
+
   async spawnServer(
     operatorId: number | WorkerId,
     port = 3333,

+ 28 - 13
tests/network-tests/src/cli/utils.ts

@@ -1,5 +1,4 @@
-import os from 'os'
-import fs from 'fs'
+import fs, { mkdirSync, rmSync } from 'fs'
 import path from 'path'
 import { v4 as uuid } from 'uuid'
 import { ChildProcessWithoutNullStreams } from 'child_process'
@@ -8,18 +7,34 @@ 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 class TmpFileManager {
+  tmpDataDir: string
 
-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
+  constructor(baseDir?: string) {
+    this.tmpDataDir = path.join(
+      baseDir || process.env.DATA_PATH || path.join(__filename, '../../../data'),
+      'joystream-testing',
+      uuid()
+    )
+    mkdirSync(this.tmpDataDir, { recursive: true })
+    nodeCleanup(() => {
+      rmSync(this.tmpDataDir, { recursive: true, force: true })
+    })
+  }
+
+  public jsonFile(value: unknown): string {
+    const tmpFilePath = path.join(this.tmpDataDir, `${uuid()}.json`)
+    fs.writeFileSync(tmpFilePath, JSON.stringify(value))
+    return tmpFilePath
+  }
+
+  public 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(this.tmpDataDir, `${uuid()}.bmp`)
+    fs.writeFileSync(tmpFilePath, rawBmp.data)
+    return tmpFilePath
+  }
 }
 
 type OutputType = 'stdout' | 'stderr'

+ 71 - 0
tests/network-tests/src/flows/clis/createChannel.ts

@@ -0,0 +1,71 @@
+import { FlowProps } from '../../Flow'
+import { extendDebug } from '../../Debugger'
+import { JoystreamCLI } from '../../cli/joystream'
+import { BuyMembershipHappyCaseFixture } from '../../fixtures/membershipModule'
+import { BN } from '@polkadot/util'
+import { FixtureRunner } from '../../Fixture'
+import { TmpFileManager } from '../../cli/utils'
+import { assert } from 'chai'
+import { Utils } from '../../utils'
+import { statSync } from 'fs'
+
+export default async function createChannel({ api, env, query }: FlowProps): Promise<void> {
+  const debug = extendDebug('flow:createChannel')
+  debug('Started')
+
+  // 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.key.address], paidTermId)
+  await new FixtureRunner(buyMembershipFixture).run()
+
+  // Send some funds to pay the deletion_prize
+  const channelOwnerBalance = api.consts.storage.dataObjectDeletionPrize.muln(2)
+  await api.treasuryTransferBalance(channelOwnerKeypair.key.address, channelOwnerBalance)
+
+  // Create CLI's
+  const tmpFileManager = new TmpFileManager()
+  const joystreamCli = new JoystreamCLI(tmpFileManager)
+
+  // Import & select channel owner key in Joystream CLI
+  await joystreamCli.importKey(channelOwnerKeypair.key)
+  await joystreamCli.chooseKey(channelOwnerKeypair.key.address)
+
+  // Create channel
+  const avatarPhotoPath = tmpFileManager.randomImgFile(300, 300)
+  const coverPhotoPath = tmpFileManager.randomImgFile(1920, 500)
+  const channelInput = {
+    title: 'Test channel',
+    avatarPhotoPath,
+    coverPhotoPath,
+    description: 'This is a test channel',
+    isPublic: true,
+    language: 'en',
+    rewardAccount: channelOwnerKeypair.key.address,
+  }
+  const { out: createChannelOut } = await joystreamCli.createChannel(channelInput, ['--context', 'Member'])
+
+  const channelIdMatch = /Channel with id ([0-9]+) successfully created/.exec(createChannelOut)
+  if (!channelIdMatch) {
+    throw new Error(`No channel id found in output:\n${createChannelOut}`)
+  }
+  const [, channelId] = channelIdMatch
+
+  await query.tryQueryWithTimeout(
+    () => query.channelById(channelId),
+    (channel) => {
+      Utils.assert(channel, 'Channel not found')
+      assert.equal(channel.title, channelInput.title)
+      assert.equal(channel.description, channelInput.description)
+      assert.equal(channel.isPublic, channelInput.isPublic)
+      assert.equal(channel.language?.iso, channelInput.language)
+      assert.equal(channel.rewardAccount, channelInput.rewardAccount)
+      assert.equal(channel.avatarPhoto?.type.__typename, 'DataObjectTypeChannelAvatar')
+      assert.equal(channel.avatarPhoto?.size, statSync(avatarPhotoPath).size)
+      assert.equal(channel.coverPhoto?.type.__typename, 'DataObjectTypeChannelCoverPhoto')
+      assert.equal(channel.coverPhoto?.size, statSync(coverPhotoPath).size)
+    }
+  )
+
+  debug('Done')
+}

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


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


+ 4 - 4
tests/network-tests/src/flows/storagev2/initStorage.ts

@@ -41,9 +41,9 @@ export const singleBucketConfig: InitStorageConfig = {
   },
   buckets: [
     {
-      metadata: { endpoint: process.env.STORAGE_1_URL || 'http://localhost:3333' },
+      metadata: { endpoint: process.env.COLOSSUS_1_URL || 'http://localhost:3333' },
       staticBags: allStaticBags,
-      operatorId: parseInt(process.env.STORAGE_1_WORKER_ID || '0'),
+      operatorId: parseInt(process.env.COLOSSUS_1_WORKER_ID || '0'),
       storageLimit: new BN(1_000_000_000_000),
       objectsLimit: 1000000000,
     },
@@ -57,9 +57,9 @@ export const doubleBucketConfig: InitStorageConfig = {
   },
   buckets: [
     {
-      metadata: { endpoint: process.env.STORAGE_1_URL || 'http://localhost:3333' },
+      metadata: { endpoint: process.env.COLOSSUS_1_URL || 'http://localhost:3333' },
       staticBags: allStaticBags,
-      operatorId: parseInt(process.env.STORAGE_1_WORKER_ID || '0'),
+      operatorId: parseInt(process.env.COLOSSUS_1_WORKER_ID || '0'),
       storageLimit: new BN(1_000_000_000_000),
       objectsLimit: 1000000000,
     },

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

@@ -1,92 +0,0 @@
-import { FlowProps } from '../../Flow'
-import { extendDebug } from '../../Debugger'
-import { WorkingGroups } from '../../WorkingGroups'
-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.Distribution)
-  const distributionLeader = await api.getGroupLead(WorkingGroups.Distribution)
-  const storageLeaderId = await api.getLeadWorkerId(WorkingGroups.Storage)
-  const storageLeader = await api.getGroupLead(WorkingGroups.Storage)
-  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.key.address], paidTermId)
-  await new FixtureRunner(buyMembershipFixture).run()
-
-  // Send some funds to pay the deletion_prize
-  const channelOwnerBalance = new BN(100)
-  await api.treasuryTransferBalance(channelOwnerKeypair.key.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.key)
-  await joystreamCli.run('account:choose', ['--address', channelOwnerKeypair.key.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.key.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')
-}

+ 117 - 6
tests/network-tests/src/graphql/generated/queries.ts

@@ -1,6 +1,35 @@
 import * as Types from './schema'
 
 import gql from 'graphql-tag'
+type DataObjectTypeFields_DataObjectTypeChannelAvatar_Fragment = {
+  __typename: 'DataObjectTypeChannelAvatar'
+  channel?: Types.Maybe<{ id: string }>
+}
+
+type DataObjectTypeFields_DataObjectTypeChannelCoverPhoto_Fragment = {
+  __typename: 'DataObjectTypeChannelCoverPhoto'
+  channel?: Types.Maybe<{ id: string }>
+}
+
+type DataObjectTypeFields_DataObjectTypeVideoMedia_Fragment = {
+  __typename: 'DataObjectTypeVideoMedia'
+  video?: Types.Maybe<{ id: string }>
+}
+
+type DataObjectTypeFields_DataObjectTypeVideoThumbnail_Fragment = {
+  __typename: 'DataObjectTypeVideoThumbnail'
+  video?: Types.Maybe<{ id: string }>
+}
+
+type DataObjectTypeFields_DataObjectTypeUnknown_Fragment = { __typename: 'DataObjectTypeUnknown' }
+
+export type DataObjectTypeFieldsFragment =
+  | DataObjectTypeFields_DataObjectTypeChannelAvatar_Fragment
+  | DataObjectTypeFields_DataObjectTypeChannelCoverPhoto_Fragment
+  | DataObjectTypeFields_DataObjectTypeVideoMedia_Fragment
+  | DataObjectTypeFields_DataObjectTypeVideoThumbnail_Fragment
+  | DataObjectTypeFields_DataObjectTypeUnknown_Fragment
+
 export type StorageDataObjectFieldsFragment = {
   id: string
   ipfsHash: string
@@ -10,11 +39,25 @@ export type StorageDataObjectFieldsFragment = {
   unsetAt?: Types.Maybe<any>
   storageBagId: string
   type:
-    | { __typename: 'DataObjectTypeChannelAvatar' }
-    | { __typename: 'DataObjectTypeChannelCoverPhoto' }
-    | { __typename: 'DataObjectTypeVideoMedia' }
-    | { __typename: 'DataObjectTypeVideoThumbnail' }
-    | { __typename: 'DataObjectTypeUnknown' }
+    | DataObjectTypeFields_DataObjectTypeChannelAvatar_Fragment
+    | DataObjectTypeFields_DataObjectTypeChannelCoverPhoto_Fragment
+    | DataObjectTypeFields_DataObjectTypeVideoMedia_Fragment
+    | DataObjectTypeFields_DataObjectTypeVideoThumbnail_Fragment
+    | DataObjectTypeFields_DataObjectTypeUnknown_Fragment
+}
+
+export type ChannelFieldsFragment = {
+  title?: Types.Maybe<string>
+  description?: Types.Maybe<string>
+  isPublic?: Types.Maybe<boolean>
+  rewardAccount?: Types.Maybe<string>
+  isCensored: boolean
+  language?: Types.Maybe<{ iso: string }>
+  ownerMember?: Types.Maybe<{ id: string }>
+  ownerCuratorGroup?: Types.Maybe<{ id: string }>
+  category?: Types.Maybe<{ name?: Types.Maybe<string> }>
+  avatarPhoto?: Types.Maybe<StorageDataObjectFieldsFragment>
+  coverPhoto?: Types.Maybe<StorageDataObjectFieldsFragment>
 }
 
 export type GetDataObjectsByIdsQueryVariables = Types.Exact<{
@@ -23,6 +66,37 @@ export type GetDataObjectsByIdsQueryVariables = Types.Exact<{
 
 export type GetDataObjectsByIdsQuery = { storageDataObjects: Array<StorageDataObjectFieldsFragment> }
 
+export type GetChannelByIdQueryVariables = Types.Exact<{
+  id: Types.Scalars['ID']
+}>
+
+export type GetChannelByIdQuery = { channelByUniqueInput?: Types.Maybe<ChannelFieldsFragment> }
+
+export const DataObjectTypeFields = gql`
+  fragment DataObjectTypeFields on DataObjectType {
+    __typename
+    ... on DataObjectTypeChannelAvatar {
+      channel {
+        id
+      }
+    }
+    ... on DataObjectTypeChannelCoverPhoto {
+      channel {
+        id
+      }
+    }
+    ... on DataObjectTypeVideoThumbnail {
+      video {
+        id
+      }
+    }
+    ... on DataObjectTypeVideoMedia {
+      video {
+        id
+      }
+    }
+  }
+`
 export const StorageDataObjectFields = gql`
   fragment StorageDataObjectFields on StorageDataObject {
     id
@@ -30,12 +104,41 @@ export const StorageDataObjectFields = gql`
     isAccepted
     size
     type {
-      __typename
+      ...DataObjectTypeFields
     }
     deletionPrize
     unsetAt
     storageBagId
   }
+  ${DataObjectTypeFields}
+`
+export const ChannelFields = gql`
+  fragment ChannelFields on Channel {
+    title
+    description
+    isPublic
+    language {
+      iso
+    }
+    rewardAccount
+    isCensored
+    ownerMember {
+      id
+    }
+    ownerCuratorGroup {
+      id
+    }
+    category {
+      name
+    }
+    avatarPhoto {
+      ...StorageDataObjectFields
+    }
+    coverPhoto {
+      ...StorageDataObjectFields
+    }
+  }
+  ${StorageDataObjectFields}
 `
 export const GetDataObjectsByIds = gql`
   query getDataObjectsByIds($ids: [ID!]) {
@@ -45,3 +148,11 @@ export const GetDataObjectsByIds = gql`
   }
   ${StorageDataObjectFields}
 `
+export const GetChannelById = gql`
+  query getChannelById($id: ID!) {
+    channelByUniqueInput(where: { id: $id }) {
+      ...ChannelFields
+    }
+  }
+  ${ChannelFields}
+`

+ 57 - 1
tests/network-tests/src/graphql/queries/storagev2.graphql

@@ -1,18 +1,74 @@
+fragment DataObjectTypeFields on DataObjectType {
+  __typename
+  ... on DataObjectTypeChannelAvatar {
+    channel {
+      id
+    }
+  }
+  ... on DataObjectTypeChannelCoverPhoto {
+    channel {
+      id
+    }
+  }
+  ... on DataObjectTypeVideoThumbnail {
+    video {
+      id
+    }
+  }
+  ... on DataObjectTypeVideoMedia {
+    video {
+      id
+    }
+  }
+}
+
 fragment StorageDataObjectFields on StorageDataObject {
   id
   ipfsHash
   isAccepted
   size
   type {
-    __typename
+    ...DataObjectTypeFields
   }
   deletionPrize
   unsetAt
   storageBagId
 }
 
+fragment ChannelFields on Channel {
+  title
+  description
+  isPublic
+  language {
+    iso
+  }
+  rewardAccount
+  isCensored
+  ownerMember {
+    id
+  }
+  ownerCuratorGroup {
+    id
+  }
+  category {
+    name
+  }
+  avatarPhoto {
+    ...StorageDataObjectFields
+  }
+  coverPhoto {
+    ...StorageDataObjectFields
+  }
+}
+
 query getDataObjectsByIds($ids: [ID!]) {
   storageDataObjects(where: { id_in: $ids }) {
     ...StorageDataObjectFields
   }
 }
+
+query getChannelById($id: ID!) {
+  channelByUniqueInput(where: { id: $id }) {
+    ...ChannelFields
+  }
+}

+ 45 - 0
tests/network-tests/src/scenarios/combined.ts

@@ -0,0 +1,45 @@
+import creatingMemberships from '../flows/membership/creatingMemberships'
+import leaderSetup from '../flows/workingGroup/leaderSetup'
+import atLeastValueBug from '../flows/workingGroup/atLeastValueBug'
+import { manageWorkerFlow } from '../flows/workingGroup/manageWorkerAsLead'
+import manageWorkerAsWorker from '../flows/workingGroup/manageWorkerAsWorker'
+import workerPayout from '../flows/workingGroup/workerPayout'
+import initDistributionBucket from '../flows/clis/initDistributionBucket'
+import initStorageBucket from '../flows/clis/initStorageBucket'
+import createChannel from '../flows/clis/createChannel'
+import { scenario } from '../Scenario'
+import { WorkingGroups } from '../WorkingGroups'
+
+scenario(async ({ job }) => {
+  // These tests assume:
+  // - storage setup (including hired lead)
+  // - existing council
+  job('creating members', creatingMemberships)
+
+  const leadSetupJob = job('setup leads', [
+    leaderSetup(WorkingGroups.Storage, true),
+    leaderSetup(WorkingGroups.Content, true),
+    leaderSetup(WorkingGroups.Distribution, true),
+  ])
+
+  // 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, workerPayout.distribution]).requires(leadSetupJob)
+
+  job('working group tests', [
+    manageWorkerFlow(WorkingGroups.Storage),
+    manageWorkerAsWorker.storage,
+    manageWorkerFlow(WorkingGroups.Content),
+    manageWorkerAsWorker.content,
+    manageWorkerFlow(WorkingGroups.Distribution),
+    manageWorkerAsWorker.distribution,
+  ]).requires(leadSetupJob)
+
+  const initBuckets = job('init storage and distribution buckets via CLI', [
+    initDistributionBucket,
+    initStorageBucket,
+  ]).requires(leadSetupJob)
+  job('create channel via CLI', createChannel).requires(initBuckets)
+})

+ 0 - 66
tests/network-tests/src/scenarios/full.ts

@@ -1,66 +0,0 @@
-import creatingMemberships from '../flows/membership/creatingMemberships'
-import councilSetup from '../flows/council/setup'
-import leaderSetup from '../flows/workingGroup/leaderSetup'
-import electionParametersProposal from '../flows/proposals/electionParametersProposal'
-import manageLeaderRole from '../flows/proposals/manageLeaderRole'
-import spendingProposal from '../flows/proposals/spendingProposal'
-import textProposal from '../flows/proposals/textProposal'
-import validatorCountProposal from '../flows/proposals/validatorCountProposal'
-import wgMintCapacityProposal from '../flows/proposals/workingGroupMintCapacityProposal'
-import atLeastValueBug from '../flows/workingGroup/atLeastValueBug'
-import { manageWorkerFlow } 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'
-import { WorkingGroups } from '../WorkingGroups'
-
-scenario(async ({ job }) => {
-  job('creating members', creatingMemberships)
-
-  const councilJob = job('council setup', councilSetup)
-
-  const proposalsJob = job('proposals', [
-    electionParametersProposal,
-    spendingProposal,
-    textProposal,
-    validatorCountProposal,
-    wgMintCapacityProposal.storage,
-    wgMintCapacityProposal.content,
-    wgMintCapacityProposal.distribution,
-    manageLeaderRole.storage,
-    manageLeaderRole.content,
-    manageLeaderRole.distribution,
-  ]).requires(councilJob)
-
-  const leadSetupJob = job('setup leads', [
-    leaderSetup(WorkingGroups.Storage),
-    leaderSetup(WorkingGroups.Content),
-    leaderSetup(WorkingGroups.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, workerPayout.distribution])
-    .requires(leadSetupJob)
-    .requires(councilJob)
-
-  job('working group tests', [
-    manageWorkerFlow(WorkingGroups.Storage),
-    manageWorkerAsWorker.storage,
-    manageWorkerFlow(WorkingGroups.Content),
-    manageWorkerAsWorker.content,
-    manageWorkerFlow(WorkingGroups.Distribution),
-    manageWorkerAsWorker.distribution,
-  ]).requires(leadSetupJob)
-
-  const initBuckets = job('init storage and distribution buckets', [
-    initDistributionBucket,
-    initStorageBucket,
-  ]).requires(leadSetupJob)
-  job('create channel', createChannel).requires(initBuckets)
-})

+ 16 - 0
tests/network-tests/src/scenarios/init-storage-and-distribution.ts

@@ -0,0 +1,16 @@
+import leaderSetup from '../flows/workingGroup/leaderSetup'
+import initStorage, { singleBucketConfig as defaultStorageConfig } from '../flows/storagev2/initStorage'
+import initDistribution, { singleBucketConfig as defaultDistributionConfig } from '../flows/storagev2/initDistribution'
+import { scenario } from '../Scenario'
+import { WorkingGroups } from '../WorkingGroups'
+import updateAccountsFlow from '../misc/updateAllWorkerRoleAccountsFlow'
+
+scenario(async ({ job }) => {
+  const setupLead = job('setup leads', [
+    leaderSetup(WorkingGroups.Distribution, true),
+    leaderSetup(WorkingGroups.Storage, true),
+  ])
+  const updateWorkerAccounts = job('Update worker accounts', updateAccountsFlow).after(setupLead)
+  job('initialize storage system', initStorage(defaultStorageConfig)).after(updateWorkerAccounts)
+  job('initialize distribution system', initDistribution(defaultDistributionConfig)).after(updateWorkerAccounts)
+})

+ 0 - 16
tests/network-tests/src/scenarios/initStorageV2.ts

@@ -1,16 +0,0 @@
-import leaderSetup from '../flows/workingGroup/leaderSetup'
-import initStorage, { defaultSingleBucketConfig as defaultStorageConfig } from '../flows/storagev2/initStorage'
-import initDistribution, {
-  defaultSingleBucketConfig as defaultDistributionConfig,
-} from '../flows/storagev2/initDistribution'
-import { scenario } from '../Scenario'
-import { WorkingGroups } from '../WorkingGroups'
-
-scenario(async ({ job }) => {
-  const setupLead = job('setup leads', [
-    leaderSetup(WorkingGroups.Distribution, true),
-    leaderSetup(WorkingGroups.Storage, true),
-  ])
-  job('initialize storage system', initStorage(defaultStorageConfig)).requires(setupLead)
-  job('initialize distribution system', initDistribution(defaultDistributionConfig)).requires(setupLead)
-})

+ 28 - 0
tests/network-tests/src/scenarios/proposals.ts

@@ -0,0 +1,28 @@
+import creatingMemberships from '../flows/membership/creatingMemberships'
+import councilSetup from '../flows/council/setup'
+import electionParametersProposal from '../flows/proposals/electionParametersProposal'
+import manageLeaderRole from '../flows/proposals/manageLeaderRole'
+import spendingProposal from '../flows/proposals/spendingProposal'
+import textProposal from '../flows/proposals/textProposal'
+import validatorCountProposal from '../flows/proposals/validatorCountProposal'
+import wgMintCapacityProposal from '../flows/proposals/workingGroupMintCapacityProposal'
+import { scenario } from '../Scenario'
+
+scenario(async ({ job }) => {
+  job('creating members', creatingMemberships)
+
+  const councilJob = job('council setup', councilSetup)
+
+  job('proposals', [
+    electionParametersProposal,
+    spendingProposal,
+    textProposal,
+    validatorCountProposal,
+    wgMintCapacityProposal.storage,
+    wgMintCapacityProposal.content,
+    wgMintCapacityProposal.distribution,
+    manageLeaderRole.storage,
+    manageLeaderRole.content,
+    manageLeaderRole.distribution,
+  ]).requires(councilJob)
+})

+ 2 - 4
tests/network-tests/src/scenarios/setup-new-chain.ts

@@ -2,10 +2,8 @@ import assignCouncil from '../flows/council/assign'
 import leaderSetup from '../flows/workingGroup/leaderSetup'
 import mockContentFlow from '../misc/mockContentFlow'
 import updateAccountsFlow from '../misc/updateAllWorkerRoleAccountsFlow'
-import initStorage, { defaultSingleBucketConfig as defaultStorageConfig } from '../flows/storagev2/initStorage'
-import initDistribution, {
-  defaultSingleBucketConfig as defaultDistributionConfig,
-} from '../flows/storagev2/initDistribution'
+import initStorage, { singleBucketConfig as defaultStorageConfig } from '../flows/storagev2/initStorage'
+import initDistribution, { singleBucketConfig as defaultDistributionConfig } from '../flows/storagev2/initDistribution'
 import { AllWorkingGroups } from '../WorkingGroups'
 import { scenario } from '../Scenario'
 

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

@@ -114,7 +114,7 @@ export class Sender {
     // Instead use a single lock for all calls, to force all transactions to be submitted in same order
     // of call to signAndSend. Otherwise it raises chance of race conditions.
     // It happens in rare cases and has lead some tests to fail occasionally in the past
-    await Sender.asyncLock.acquire('tx-queue', async () => {
+    await Sender.asyncLock.acquire(['tx-queue', `nonce-${account.toString()}`], async () => {
       const nonce = await this.api.rpc.system.accountNextIndex(senderKeyPair.address)
       const signedTx = tx.sign(senderKeyPair, { nonce })
       sentTx = signedTx.toHuman()

+ 6 - 0
tests/network-tests/src/utils.ts

@@ -63,4 +63,10 @@ export class Utils {
     // We use `toObject()` to get rid of .prototype defaults for optional fields
     return metaToObject(metaClass, metaClass.decode(bytes.toU8a(true)))
   }
+
+  public static assert(condition: any, msg?: string): asserts condition {
+    if (!condition) {
+      throw new Error(msg || 'Assertion failed')
+    }
+  }
 }