UNPKG

@noggin/elastic-noggin-sdk

Version:
657 lines (598 loc) 21.4 kB
import { startProcess, getProcessStatus } from "./process"; import { Batch } from "./models/types"; import { EnoFactory } from "./EnoFactory"; import { IEnSrvOptions } from "./IEnSrvOptions"; import * as Send from "./send"; import { delay, map, of, switchMap, throwError, TimeoutError } from "rxjs"; const testEnSrvOptions: IEnSrvOptions = { enSrvUrl: "http://example.com", namespace: "myNameSpace", }; describe("process", () => { it("Should start a process", (done) => { let callCount = 0; spyOn(Send, "send").and.callFake((batch: Batch) => { if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } expect(batch.length).toBe(1); expect(batch[0].getType()).toBe("op/process"); expect(batch[0].getFieldStringValue("op/process/process")).toBe( "test-process-tip" ); expect(batch[0].getFieldJsonValue("op/process/inline-vars")).toEqual({ myinputkey: ["myinputval"], }); const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/inline-vars", [ JSON.stringify({ myoutputkey: ["myoutputval"] }), ]); enoFactory.setField("response/process/finished", ["true"]); const responseEno = enoFactory.makeEno(); return of([responseEno]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, inputVars: { myinputkey: ["myinputval"] }, }).subscribe({ next: (response) => { expect(response.isFinished).toBeTruthy(); expect(response.outputVars).toEqual({ myoutputkey: ["myoutputval"] }); done(); }, error: (err) => { throw err; }, }); }); it("Should not start a non-existent process", (done) => { const enoFactory = new EnoFactory("error"); enoFactory.setSecurity("security/policy/local"); enoFactory.setField("error/message/tip", ["error/message/eno/not-found"]); const eno = enoFactory.makeEno(); let callCount = 0; spyOn(Send, "send").and.callFake((batch: Batch) => { if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } return of([eno]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, inputVars: { myinputkey: ["myinputval"] }, }).subscribe({ next: (_) => { throw "Should not have executed process"; }, error: (err) => { expect(err.message).toContain("error/message/eno/not-found"); done(); }, }); }); it("Should get status of a process", (done) => { const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", ["test-process-op-tip"]); enoFactory.setField("response/process/inline-vars", [ JSON.stringify({ myoutputkey: ["myoutputval"] }), ]); enoFactory.setField("response/process/finished", ["true"]); const responseEno = enoFactory.makeEno(); const sendSpy = spyOn(Send, "send").and.returnValue(of([responseEno])); getProcessStatus("test-process-op-tip", testEnSrvOptions).subscribe({ next: (processResponse) => { expect(sendSpy).toHaveBeenCalledWith( jasmine.arrayContaining([ jasmine.objectContaining({ source: jasmine.objectContaining({ type: "op/process/status", security: "security/policy/op", field: [ { tip: "op/process/status:op-tip", value: ["test-process-op-tip"], }, ], }), }), ]), testEnSrvOptions ); expect(processResponse).toEqual({ operationTip: "test-process-op-tip", responseTip: responseEno.tip, outputVars: { myoutputkey: ["myoutputval"] }, isFinished: true, }); done(); }, error: (err) => { console.error(err); fail(); done(); }, }); }); it("Should fail to get status of a process", (done) => { spyOn(Send, "send").and.returnValue(of([])); getProcessStatus("test-process-op-tip", testEnSrvOptions).subscribe({ next: () => { fail(); done(); }, error: (err) => { expect(err.message).toBe("error/message/server/internal"); done(); }, }); }); it("Should retry when not finished", (done) => { let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch) => { const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } else if (callCount === 1) { enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 2) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 3) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["true"]); } callCount++; return of([enoFactory.makeEno()]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, retryDelayMs: 1, retryAttempts: 5, }).subscribe({ next: (response) => { expect(sendSpy).toHaveBeenCalledTimes(4); expect(response.isFinished).toBeTrue(); done(); }, error: (err) => { console.error(err); fail(); done(); }, }); }); it("Should exhaust attempts when not finished", (done) => { let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch) => { const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } else if (callCount === 1) { enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 2) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 3) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["true"]); } callCount++; return of([enoFactory.makeEno()]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, retryDelayMs: 1, retryAttempts: 1, }).subscribe({ next: () => { fail(); done(); }, error: (err) => { expect(sendSpy).toHaveBeenCalledTimes(3); expect(err.message).toBe( "Too many attempts waiting for process to finish" ); done(); }, }); }); it("Should not retry if not waiting", (done) => { let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch) => { const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } else if (callCount === 1) { enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 2) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 3) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["true"]); } callCount++; return of([enoFactory.makeEno()]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: false, retryDelayMs: 1, retryAttempts: 5, }).subscribe({ next: (response) => { expect(sendSpy).toHaveBeenCalledTimes(2); expect(response.isFinished).toBeFalse(); done(); }, error: (err) => { console.error(err); fail(); done(); }, }); }); it("Should fetch the status if the process started but failed to respond", (done) => { let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch) => { const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } else if (callCount === 1) { callCount++; return throwError(() => new Error("Deliberate error")); } else if (callCount === 2) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["false"]); } else if (callCount === 3) { enoFactory.setField( "response/process/op-tip", batch[0].getFieldValues("op/process/status:op-tip") ); enoFactory.setField("response/process/finished", ["true"]); } callCount++; return of([enoFactory.makeEno()]); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, retryDelayMs: 1, retryAttempts: 5, }).subscribe({ next: (response) => { expect(sendSpy).toHaveBeenCalledTimes(4); expect(response.isFinished).toBeTrue(); done(); }, error: (err) => { console.error(err); fail(); done(); }, }); }); it("Should fetch the status if the process did not start and failed to respond", (done) => { let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch) => { if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } return throwError(() => new Error("Deliberate error")); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: true, retryDelayMs: 1, retryAttempts: 5, }).subscribe({ next: (response) => { fail(); done(); }, error: (err) => { expect(sendSpy).toHaveBeenCalledTimes(3); done(); }, }); }); it("Should fetch the status if the op/process started but timed out", (done) => { let callCount = 0; const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); const sendSpy = spyOn(Send, "send").and.callFake((batch) => { if (callCount === 0) { // Empty batch for session initialization expect(batch.length).toBe(0); callCount++; return of([]); } else if (callCount === 1 && batch[0].getType() === 'op/process') { enoFactory.setField('response/process/op-tip', [batch[0].tip]); callCount++; return of(null).pipe( delay(3000), map(() => [enoFactory.makeEno()]) ); } else if (batch[0].getType() === 'op/process/status') { return of([enoFactory.makeEno()]); } return throwError(() => new Error('Unexpected operation')); }); startProcess("test-process-tip", testEnSrvOptions, { waitForFinish: false, retryDelayMs: 1, retryAttempts: 1, timeoutMs: 2000, }).subscribe({ next: (response) => { expect(sendSpy).toHaveBeenCalledTimes(3); done(); }, error: (err) => { fail(); done(); }, }); }); // Table-driven test for session initialization failures [ { description: "timeout error", errorFactory: () => throwError(() => new TimeoutError()), }, { description: "deliberate exception", errorFactory: () => throwError(() => new Error("Deliberate session initialization error")), }, { description: "503 status code with internal server error ENO", errorFactory: () => { const enoFactory = new EnoFactory("error"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("error/message/tip", ["error/message/server/internal"]); const errorEno = enoFactory.makeEno(); return of([errorEno]); }, isEnoResponse: true, }, ].forEach(({ errorFactory, description, isEnoResponse }) => { it(`Should proceed with process execution on session initialization failure: ${description}`, (done) => { const opts = { enSrvUrl: "http://example.com", namespace: "myNameSpace" }; let callCount = 0; const sendSpy = spyOn(Send, "send").and.callFake((batch: Batch, options: any) => { if (callCount === 0) { expect(batch.length).toBe(0); expect(options.maintainInitialSessionToken).toBe(true); callCount++; return errorFactory(); } // Second call should be the actual process execution expect(batch.length).toBe(1); expect(batch[0].getType()).toBe("op/process"); expect(batch[0].getFieldStringValue("op/process/process")).toBe("test-process-tip"); expect(options.maintainInitialSessionToken).toBe(true); const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["true"]); const responseEno = enoFactory.makeEno(); return of([responseEno]); }); startProcess("test-process-tip", opts, { waitForFinish: true }).subscribe({ next: (response) => { // Verify that both calls were made: session init (failed) and process execution (succeeded) expect(sendSpy).toHaveBeenCalledTimes(2); // Verify first call was session initialization (empty batch) expect(sendSpy.calls.argsFor(0)[0]).toEqual([]); // Verify second call was process execution expect(sendSpy.calls.argsFor(1)[0].length).toBe(1); expect(sendSpy.calls.argsFor(1)[0][0].getType()).toBe("op/process"); expect(response.isFinished).toBeTruthy(); done(); }, error: (err) => { fail(`Should not have failed: ${err.message}`); done(); }, }); }); }); it("Should skip session initialization when sessionToken is already set", (done) => { const opts = { enSrvUrl: "http://example.com", namespace: "myNameSpace", sessionToken: "existing-session-token", }; const sendSpy = spyOn(Send, "send").and.callFake((batch: Batch, options: any) => { // The batch is non-empty. Empty batch is only sent for session initialization. expect(batch.length).toBe(1); expect(batch[0].getType()).toBe("op/process"); expect(batch[0].getFieldStringValue("op/process/process")).toBe("test-process-tip"); expect(options.maintainInitialSessionToken).toBe(true); const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["true"]); return of([enoFactory.makeEno()]); }); startProcess("test-process-tip", opts, { waitForFinish: true }).subscribe({ next: (response) => { // Assert there was no session initialization expect(sendSpy).toHaveBeenCalledTimes(1); expect(sendSpy.calls.argsFor(0)[0].length).toBe(1); expect(sendSpy.calls.argsFor(0)[0][0].getType()).toBe("op/process"); expect(response.isFinished).toBeTruthy(); done(); }, error: (err) => { fail(`Should not have failed: ${err.message}`); done(); }, }); }); // Table-driven test for maintainInitialSessionToken reversion [false, true, undefined].forEach((originalValue) => { [ { description: "successful process completion", setupSendSpy: (sendSpy: jasmine.Spy) => { let callCount = 0; sendSpy.and.callFake((batch: Batch) => { if (callCount === 0) { // Empty batch for session initialization callCount++; return of([]); } const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["true"]); return of([enoFactory.makeEno()]); }); }, expectError: false, }, { description: "process execution error", setupSendSpy: (sendSpy: jasmine.Spy) => { let callCount = 0; sendSpy.and.callFake((batch: Batch) => { if (callCount === 0) { // Empty batch for session initialization callCount++; return of([]); } return throwError(() => new Error("Process execution failed")); }); }, expectError: true, }, { description: "timeout during process execution", setupSendSpy: (sendSpy: jasmine.Spy) => { let callCount = 0; sendSpy.and.callFake((batch: Batch) => { if (callCount === 0) { // Empty batch for session initialization callCount++; return of([]); } return of(null).pipe( delay(3000), map(() => []) ); }); }, expectError: true, }, { description: "existing session token present", setupSendSpy: (sendSpy: jasmine.Spy) => { sendSpy.and.callFake((batch: Batch) => { // No session initialization - directly process execution const enoFactory = new EnoFactory("response/process"); enoFactory.setSecurity("security/policy/everyone"); enoFactory.setField("response/process/op-tip", [batch[0].tip]); enoFactory.setField("response/process/finished", ["true"]); return of([enoFactory.makeEno()]); }); }, expectError: false, hasExistingSessionToken: true, }, ].forEach(({ description, setupSendSpy, expectError, hasExistingSessionToken }) => { it(`Should revert maintainInitialSessionToken on ${description} with original ${originalValue}`, (done) => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 500000; const opts: any = { enSrvUrl: "http://example.com", namespace: "myNameSpace", }; if (originalValue !== undefined) { opts.maintainInitialSessionToken = originalValue; } if (hasExistingSessionToken) { opts.sessionToken = "existing-session-token"; } const sendSpy = spyOn(Send, "send"); setupSendSpy(sendSpy); const checkAndComplete = () => { setTimeout(() => { expect(opts.maintainInitialSessionToken).toBe(originalValue); done(); }, 0); }; startProcess("test-process-tip", opts, { waitForFinish: false, timeoutMs: 1000, }).subscribe({ next: () => { if (expectError) { fail("Expected an error but got success"); } }, error: () => { if (!expectError) { fail("Expected success but got an error"); } checkAndComplete(); }, complete: () => { if (!expectError) { checkAndComplete(); } }, }); }); }); }); });