runRequest.test.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { DataFrame, LoadingState, dateTime } from '@grafana/data';
  2. import { PanelData, DataSourceApi, DataQueryRequest, DataQueryResponse } from '@grafana/ui';
  3. import { Subscriber, Observable, Subscription } from 'rxjs';
  4. import { runRequest } from './runRequest';
  5. jest.mock('app/core/services/backend_srv');
  6. class ScenarioCtx {
  7. ds: DataSourceApi;
  8. request: DataQueryRequest;
  9. subscriber: Subscriber<DataQueryResponse>;
  10. isUnsubbed = false;
  11. setupFn: () => void = () => {};
  12. results: PanelData[];
  13. subscription: Subscription;
  14. wasStarted = false;
  15. error: Error = null;
  16. toStartTime = dateTime();
  17. fromStartTime = dateTime();
  18. reset() {
  19. this.wasStarted = false;
  20. this.isUnsubbed = false;
  21. this.results = [];
  22. this.request = {
  23. range: {
  24. from: this.toStartTime,
  25. to: this.fromStartTime,
  26. raw: { from: '1h', to: 'now' },
  27. },
  28. targets: [
  29. {
  30. refId: 'A',
  31. },
  32. ],
  33. } as DataQueryRequest;
  34. this.ds = {
  35. query: (request: DataQueryRequest) => {
  36. return new Observable<DataQueryResponse>(subscriber => {
  37. this.subscriber = subscriber;
  38. this.wasStarted = true;
  39. if (this.error) {
  40. throw this.error;
  41. }
  42. return () => {
  43. this.isUnsubbed = true;
  44. };
  45. });
  46. },
  47. } as DataSourceApi;
  48. }
  49. start() {
  50. this.subscription = runRequest(this.ds, this.request).subscribe({
  51. next: (data: PanelData) => {
  52. this.results.push(data);
  53. },
  54. });
  55. }
  56. emitPacket(packet: DataQueryResponse) {
  57. this.subscriber.next(packet);
  58. }
  59. setup(fn: () => void) {
  60. this.setupFn = fn;
  61. }
  62. }
  63. function runRequestScenario(desc: string, fn: (ctx: ScenarioCtx) => void) {
  64. describe(desc, () => {
  65. const ctx = new ScenarioCtx();
  66. beforeEach(() => {
  67. ctx.reset();
  68. return ctx.setupFn();
  69. });
  70. fn(ctx);
  71. });
  72. }
  73. describe('runRequest', () => {
  74. runRequestScenario('with no queries', ctx => {
  75. ctx.setup(() => {
  76. ctx.request.targets = [];
  77. ctx.start();
  78. });
  79. it('should emit empty result with loading state done', () => {
  80. expect(ctx.wasStarted).toBe(false);
  81. expect(ctx.results[0].state).toBe(LoadingState.Done);
  82. });
  83. });
  84. runRequestScenario('After first response', ctx => {
  85. ctx.setup(() => {
  86. ctx.start();
  87. ctx.emitPacket({
  88. data: [{ name: 'Data' } as DataFrame],
  89. });
  90. });
  91. it('should emit single result with loading state done', () => {
  92. expect(ctx.wasStarted).toBe(true);
  93. expect(ctx.results.length).toBe(1);
  94. });
  95. });
  96. runRequestScenario('After tree responses, 2 with different keys', ctx => {
  97. ctx.setup(() => {
  98. ctx.start();
  99. ctx.emitPacket({
  100. data: [{ name: 'DataA-1' } as DataFrame],
  101. key: 'A',
  102. });
  103. ctx.emitPacket({
  104. data: [{ name: 'DataA-2' } as DataFrame],
  105. key: 'A',
  106. });
  107. ctx.emitPacket({
  108. data: [{ name: 'DataB-1' } as DataFrame],
  109. key: 'B',
  110. });
  111. });
  112. it('should emit 3 seperate results', () => {
  113. expect(ctx.results.length).toBe(3);
  114. });
  115. it('should combine results and return latest data for key A', () => {
  116. expect(ctx.results[2].series).toEqual([{ name: 'DataA-2' }, { name: 'DataB-1' }]);
  117. });
  118. it('should have loading state Done', () => {
  119. expect(ctx.results[2].state).toEqual(LoadingState.Done);
  120. });
  121. });
  122. runRequestScenario('After response with state Streaming', ctx => {
  123. ctx.setup(() => {
  124. ctx.start();
  125. ctx.emitPacket({
  126. data: [{ name: 'DataA-1' } as DataFrame],
  127. key: 'A',
  128. });
  129. ctx.emitPacket({
  130. data: [{ name: 'DataA-2' } as DataFrame],
  131. key: 'A',
  132. state: LoadingState.Streaming,
  133. });
  134. });
  135. it('should have loading state Streaming', () => {
  136. expect(ctx.results[1].state).toEqual(LoadingState.Streaming);
  137. });
  138. });
  139. runRequestScenario('If no response after 250ms', ctx => {
  140. ctx.setup(async () => {
  141. ctx.start();
  142. await sleep(250);
  143. });
  144. it('should emit 1 result with loading state', () => {
  145. expect(ctx.results.length).toBe(1);
  146. expect(ctx.results[0].state).toBe(LoadingState.Loading);
  147. });
  148. });
  149. runRequestScenario('on thrown error', ctx => {
  150. ctx.setup(() => {
  151. ctx.error = new Error('Ohh no');
  152. ctx.start();
  153. });
  154. it('should emit 1 error result', () => {
  155. expect(ctx.results[0].error.message).toBe('Ohh no');
  156. expect(ctx.results[0].state).toBe(LoadingState.Error);
  157. });
  158. });
  159. runRequestScenario('If time range is relative', ctx => {
  160. ctx.setup(async () => {
  161. ctx.start();
  162. // wait a bit
  163. await sleep(20);
  164. ctx.emitPacket({ data: [{ name: 'DataB-1' } as DataFrame] });
  165. });
  166. it('should update returned request range', () => {
  167. expect(ctx.results[0].request.range.to.valueOf()).not.toBe(ctx.fromStartTime);
  168. });
  169. });
  170. });
  171. async function sleep(ms: number) {
  172. return new Promise(resolve => {
  173. setTimeout(resolve, ms);
  174. });
  175. }