Scenario.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { WsProvider } from '@polkadot/api'
  2. import { Api } from './Api'
  3. import { QueryNodeApi } from './QueryNodeApi'
  4. import { config } from 'dotenv'
  5. import { ApolloClient, InMemoryCache } from '@apollo/client'
  6. import Debugger from 'debug'
  7. export type FlowArgs = { api: Api; env: NodeJS.ProcessEnv; query: QueryNodeApi }
  8. export type Flow = (args: FlowArgs) => Promise<void>
  9. class Job {
  10. private dependencies: Job[]
  11. private flowArgs: FlowArgs
  12. private flows: Flow[]
  13. private manager: FlowManager
  14. constructor(manager: FlowManager, flowArgs: FlowArgs, flows: Flow[]) {
  15. this.manager = manager
  16. this.flowArgs = flowArgs
  17. this.flows = flows
  18. }
  19. // Depend on another job to complete
  20. public afterSuccessOf(job: Job): Job {
  21. this.dependencies.push(job)
  22. return this
  23. }
  24. // Depend on another job to complete
  25. public afterSuccessOrFailureOf(job: Job): Job {
  26. this.dependencies.push(job)
  27. return this
  28. }
  29. // Allows job to fail (one or more flows failing) without interrupting the scenario
  30. // The scenario will still result in failure, but allows other jobs and flows to be tested
  31. public allowFailure(): Job {
  32. return this
  33. }
  34. // configure to have flows run serially instead of in parallel
  35. public serially(): Job {
  36. return this
  37. }
  38. }
  39. class FlowManager {
  40. private readonly flowArgs: FlowArgs
  41. private pendingJobs: Job[]
  42. private completedJobs: Job[]
  43. constructor(flowArgs: FlowArgs) {
  44. this.flowArgs = flowArgs
  45. }
  46. public createJob(flows: Flow[]): Job {
  47. const job = new Job(this, this.flowArgs, flows)
  48. this.pendingJobs.push(job)
  49. // TODO: return a limited interface only for configuring job before it runs
  50. return job
  51. }
  52. // Run the jobs in parallel where possible, followed by their dependents
  53. public async run(): Promise<void> {
  54. }
  55. }
  56. export async function scenario(
  57. fn: (cfg: {
  58. api: Api
  59. query: QueryNodeApi
  60. env: NodeJS.ProcessEnv
  61. debug: Debugger.Debugger
  62. job: (flows: Flow[]) => Job
  63. }) => Promise<void>
  64. ): Promise<void> {
  65. // Load env variables
  66. config()
  67. const env = process.env
  68. // Connect api to the chain
  69. const nodeUrl: string = env.NODE_URL || 'ws://127.0.0.1:9944'
  70. const provider = new WsProvider(nodeUrl)
  71. const api = await Api.create(provider, env.TREASURY_ACCOUNT_URI || '//Alice', env.SUDO_ACCOUNT_URI || '//Alice')
  72. const queryNodeUrl: string = env.QUERY_NODE_URL || 'http://127.0.0.1:8081/graphql'
  73. const queryNodeProvider = new ApolloClient({
  74. uri: queryNodeUrl,
  75. cache: new InMemoryCache(),
  76. defaultOptions: { query: { fetchPolicy: 'no-cache', errorPolicy: 'all' } },
  77. })
  78. const query = new QueryNodeApi(queryNodeProvider)
  79. const debug = Debugger('scenario')
  80. const flowManager = new FlowManager({ api, query, env })
  81. // Does the scenario really need the flow args?
  82. await fn({ api, query, env, debug, job: flowManager.createJob.bind(flowManager) })
  83. await flowManager.run()
  84. // Note: disconnecting and then reconnecting to the chain in the same process
  85. // doesn't seem to work!
  86. api.close()
  87. }