import { calcQueryTimeouts, execute, execute1d, execute1dWithResponseHeaders, IQueryOption } from "./query";
import { Batch } from "./models/types";
import * as Send from "./send";
import { EnoFactory } from "./EnoFactory";
import { IEnSrvOptions } from "./IEnSrvOptions";
import * as Locale from "./locale";
import { firstValueFrom, of } from "rxjs";
import { Eno } from "./models/Eno";

const testEnSrvOptions: IEnSrvOptions = {
  enSrvUrl: "http://example.com",
  namespace: "myNameSpace",
};

describe("query", () => {
  let nowVarSpy: jasmine.Spy;
  let getLangsSpy: jasmine.Spy;

  beforeEach(() => {
    nowVarSpy = spyOn(Locale, "nowVar").and.returnValue(
      of("2021-04-29T17:51:46+1000")
    );
    getLangsSpy = spyOn(Locale, "getLangs").and.returnValue(
      of(["fr-fr", "en-us"])
    );
  });

  it("Should execute a query", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const queryResponse = await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(nowVarSpy).toHaveBeenCalledWith(testEnSrvOptions);
    expect(getLangsSpy).toHaveBeenCalledWith(testEnSrvOptions, undefined, true);

    expect(queryResponse).toEqual({
      attributes: [
        { label: "Attribute 1", formula: 'FIELD("attr1")', tip: attrEno1.tip },
        { label: "Attribute 2", formula: 'FIELD("attr2")', tip: attrEno2.tip },
        { label: "runtimeAttr1", formula: "TIP()" },
      ],
      dimensions: [{ label: "runtimeDim1", values: ["R1", "R2"] }],
      results: [
        {
          R1: {
            "Attribute 1": "value 1-1",
            "Attribute 2": "value 1-2",
            runtimeAttr1: "value 1-R",
          },
        },
        {
          R2: {
            "Attribute 1": "value 2-1",
            "Attribute 2": "value 2-2",
            runtimeAttr1: "value 2-R",
          },
        },
      ],
      execTime: 1,
    });

    expect(queryOpEno.getType()).toBe("op/query");
    expect(queryOpEno.getFieldValues("op/query/lang")).toEqual([
      "fr-fr",
      "en-us",
    ]);
    expect(queryOpEno.getFieldValues("op/query/tip")).toEqual(["test/query"]);
    expect(queryOpEno.getFieldValues("op/query/timeout")).toEqual(["10345"]);
    expect(queryOpEno.getFieldValues("op/query/query")).toEqual([
      '{"attributes":[{"label":"runtimeAttr1","formula":"TIP()"}],"filters":[{"label":"runtimeFilter1","formula":"TIP()"}],"vars":{"varKey1":["varVal1a","varVal1b"],"varKey2":["varVal2a","varVal2b"],"---NOW---":["2021-04-29T17:51:46+1000"]},"dimensions":[{"label":"runtimeDim1","formula":"TIP()","sortby":["TITLE()"],"sortdir":["asc"],"limit":1}]}',
    ]);
  });

  it("Should execute a query with include response headers", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch, options: IEnSrvOptions, queryOptions: IQueryOption) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();
      queryOptions.responseHeadersToInclude = [{ en_query_nextpage: "123" }];

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
      responseHeadersToInclude: ['en_query_nextpage']
    };

    const queryResponse = await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(nowVarSpy).toHaveBeenCalledWith(testEnSrvOptions);
    expect(getLangsSpy).toHaveBeenCalledWith(testEnSrvOptions, undefined, true);

    expect(queryResponse).toEqual({
      attributes: [
        { label: "Attribute 1", formula: 'FIELD("attr1")', tip: attrEno1.tip },
        { label: "Attribute 2", formula: 'FIELD("attr2")', tip: attrEno2.tip },
        { label: "runtimeAttr1", formula: "TIP()" },
      ],
      dimensions: [{ label: "runtimeDim1", values: ["R1", "R2"] }],
      results: [
        {
          R1: {
            "Attribute 1": "value 1-1",
            "Attribute 2": "value 1-2",
            runtimeAttr1: "value 1-R",
          },
        },
        {
          R2: {
            "Attribute 1": "value 2-1",
            "Attribute 2": "value 2-2",
            runtimeAttr1: "value 2-R",
          },
        },
      ],
      execTime: 1,
      responseHeaders: [{ en_query_nextpage: "123" }],
    });

    expect(queryOpEno.getType()).toBe("op/query");
    expect(queryOpEno.getFieldValues("op/query/lang")).toEqual([
      "fr-fr",
      "en-us",
    ]);
    expect(queryOpEno.getFieldValues("op/query/tip")).toEqual(["test/query"]);
    expect(queryOpEno.getFieldValues("op/query/timeout")).toEqual(["10345"]);
    expect(queryOpEno.getFieldValues("op/query/query")).toEqual([
      '{"attributes":[{"label":"runtimeAttr1","formula":"TIP()"}],"filters":[{"label":"runtimeFilter1","formula":"TIP()"}],"vars":{"varKey1":["varVal1a","varVal1b"],"varKey2":["varVal2a","varVal2b"],"---NOW---":["2021-04-29T17:51:46+1000"]},"dimensions":[{"label":"runtimeDim1","formula":"TIP()","sortby":["TITLE()"],"sortdir":["asc"],"limit":1}]}',
    ]);
  });

  it("Should execute a query with lang string", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
      lang: "es-es",
      includeFallbackLang: false
    };

    await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(getLangsSpy).toHaveBeenCalledWith(testEnSrvOptions, "es-es", false);
  });

  it("Should execute a query with a lang array", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
      lang: ["es-es", "zh-zh"],
    };

    await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(getLangsSpy).toHaveBeenCalledWith(testEnSrvOptions, [
      "es-es",
      "zh-zh",
    ], true);
  });

  it("Should execute a query with a NOW variable", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
        "---NOW---": ["2021-04-13T17:29:00+1000"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const queryResponse = await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(nowVarSpy).not.toHaveBeenCalled();

    expect(queryResponse).toEqual({
      attributes: [
        { label: "Attribute 1", formula: 'FIELD("attr1")', tip: attrEno1.tip },
        { label: "Attribute 2", formula: 'FIELD("attr2")', tip: attrEno2.tip },
        { label: "runtimeAttr1", formula: "TIP()" },
      ],
      dimensions: [{ label: "runtimeDim1", values: ["R1", "R2"] }],
      results: [
        {
          R1: {
            "Attribute 1": "value 1-1",
            "Attribute 2": "value 1-2",
            runtimeAttr1: "value 1-R",
          },
        },
        {
          R2: {
            "Attribute 1": "value 2-1",
            "Attribute 2": "value 2-2",
            runtimeAttr1: "value 2-R",
          },
        },
      ],
      execTime: 1,
    });

    expect(queryOpEno.getType()).toBe("op/query");
    expect(queryOpEno.getFieldValues("op/query/tip")).toEqual(["test/query"]);
    expect(queryOpEno.getFieldValues("op/query/timeout")).toEqual(["10345"]);
    expect(queryOpEno.getFieldValues("op/query/query")).toEqual([
      '{"attributes":[{"label":"runtimeAttr1","formula":"TIP()"}],"filters":[{"label":"runtimeFilter1","formula":"TIP()"}],"vars":{"varKey1":["varVal1a","varVal1b"],"varKey2":["varVal2a","varVal2b"],"---NOW---":["2021-04-13T17:29:00+1000"]},"dimensions":[{"label":"runtimeDim1","formula":"TIP()","sortby":["TITLE()"],"sortdir":["asc"],"limit":1}]}',
    ]);
  });

  it("Should execute a query with a lastPersist value", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    let queryOpEno: Eno;

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      queryOpEno = batch[0];
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [queryOpEno.tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
        "---NOW---": ["2021-04-13T17:29:00+1000"],
      },
      lastPersist: '2021-04-13T17:29:00+1000',
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const queryResponse = await firstValueFrom(
      execute("test/query", testEnSrvOptions, queryOptions, 12345)
    );

    expect(queryOpEno.getFieldValues("op/query/query")).toEqual([
      '{"attributes":[{"label":"runtimeAttr1","formula":"TIP()"}],"filters":[{"label":"runtimeFilter1","formula":"TIP()"}],"vars":{"varKey1":["varVal1a","varVal1b"],"varKey2":["varVal2a","varVal2b"],"---NOW---":["2021-04-13T17:29:00+1000"]},"dimensions":[{"label":"runtimeDim1","formula":"TIP()","sortby":["TITLE()"],"sortdir":["asc"],"limit":1}],"lastPersist":"2021-04-13T17:29:00+1000"}',
    ]);
  });

  it("Should correctly calculate query timeouts", () => {
    [
      {
        timeoutMs: 0,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 1500 },
      },
      {
        timeoutMs: 100,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 1500 },
      },
      {
        timeoutMs: 900,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 1500 },
      },
      {
        timeoutMs: 1000,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 1500 },
      },
      {
        timeoutMs: 1500,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 1500 },
      },
      {
        timeoutMs: 2000,
        expected: { queryTimeoutMs: 1000, observableTimeoutMs: 2000 },
      },
      {
        timeoutMs: 5000,
        expected: { queryTimeoutMs: 3000, observableTimeoutMs: 5000 },
      },
      {
        timeoutMs: 10000,
        expected: { queryTimeoutMs: 8000, observableTimeoutMs: 10000 },
      },
      {
        timeoutMs: 30000,
        expected: { queryTimeoutMs: 28000, observableTimeoutMs: 30000 },
      },
      {
        timeoutMs: 40000,
        expected: { queryTimeoutMs: 28000, observableTimeoutMs: 40000 },
      },
    ].forEach((test) => {
      expect(calcQueryTimeouts(test.timeoutMs)).toEqual(test.expected);
    });
  });

  it("Should execute a one-dimensional query", async () => {
    const attrEnoFactory1 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
    attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
    const attrEno1 = attrEnoFactory1.makeEno();

    const attrEnoFactory2 = new EnoFactory(
      "query/attribute",
      "security/policy/everyone"
    );
    attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
    attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
    const attrEno2 = attrEnoFactory2.makeEno();

    spyOn(Send, "send").and.callFake((batch: Batch) => {
      const responseEnoFactory = new EnoFactory("response/query");
      responseEnoFactory.setSecurity("security/policy/everyone");
      responseEnoFactory.setField("response/query/op-tip", [batch[0].tip]);
      responseEnoFactory.setField("response/query/attributes", [
        attrEno1.tip,
        attrEno2.tip,
      ]);
      responseEnoFactory.setField("response/query/result", [
        "value 1-1",
        "value 1-2",
        "value 1-R",
        "value 2-1",
        "value 2-2",
        "value 2-R",
      ]);
      responseEnoFactory.setField("response/query/exec-time", ["1"]);
      responseEnoFactory.setField("response/query/runtime-attributes", [
        JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
      ]);
      responseEnoFactory.setField("response/query/runtime-dimensions", [
        JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
      ]);
      const responseEno = responseEnoFactory.makeEno();

      return of([responseEno, attrEno1, attrEno2]);
    });

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const queryResponse = await firstValueFrom(
      execute1d("test/query", testEnSrvOptions, queryOptions)
    );

    expect(queryResponse).toEqual([
      {
        "Attribute 1": "value 1-1",
        "Attribute 2": "value 1-2",
        runtimeAttr1: "value 1-R",
      },
      {
        "Attribute 1": "value 2-1",
        "Attribute 2": "value 2-2",
        runtimeAttr1: "value 2-R",
      },
    ]);
  });

  describe("#execute1dWithResponseHeaders", () => {
    it("Should execute with response headers", async () => {
      const attrEnoFactory1 = new EnoFactory(
        "query/attribute",
        "security/policy/everyone"
      );
      attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
      attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
      const attrEno1 = attrEnoFactory1.makeEno();
  
      const attrEnoFactory2 = new EnoFactory(
        "query/attribute",
        "security/policy/everyone"
      );
      attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
      attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
      const attrEno2 = attrEnoFactory2.makeEno();
  
      spyOn(Send, "send").and.callFake((batch: Batch, options: IEnSrvOptions, queryOptions: IQueryOption) => {
        const responseEnoFactory = new EnoFactory("response/query");
        responseEnoFactory.setSecurity("security/policy/everyone");
        responseEnoFactory.setField("response/query/op-tip", [batch[0].tip]);
        responseEnoFactory.setField("response/query/attributes", [
          attrEno1.tip,
          attrEno2.tip,
        ]);
        responseEnoFactory.setField("response/query/result", [
          "value 1-1",
          "value 1-2",
          "value 1-R",
          "value 2-1",
          "value 2-2",
          "value 2-R",
        ]);
        responseEnoFactory.setField("response/query/exec-time", ["1"]);
        responseEnoFactory.setField("response/query/runtime-attributes", [
          JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
        ]);
        responseEnoFactory.setField("response/query/runtime-dimensions", [
          JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
        ]);
        const responseEno = responseEnoFactory.makeEno();
        queryOptions.responseHeadersToInclude = [{ en_query_nextpage: "123" }];
  
        return of([responseEno, attrEno1, attrEno2]);
      });
  
      const queryOptions: IQueryOption = {
        extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
        extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
        vars: {
          varKey1: ["varVal1a", "varVal1b"],
          varKey2: ["varVal2a", "varVal2b"],
        },
        dimensionOptions: [
          {
            label: "runtimeDim1",
            formula: "TIP()",
            sortby: ["TITLE()"],
            sortdir: ["asc"],
            limit: 1,
          },
        ],
        responseHeadersToInclude: ["en_query_nextpage"],
      };
  
      const queryResponse = await firstValueFrom(
        execute1dWithResponseHeaders("test/query", testEnSrvOptions, queryOptions)
      );
  
      expect(queryResponse).toEqual({
        results: [
          {
            "Attribute 1": "value 1-1",
            "Attribute 2": "value 1-2",
            runtimeAttr1: "value 1-R",
          },
          {
            "Attribute 1": "value 2-1",
            "Attribute 2": "value 2-2",
            runtimeAttr1: "value 2-R",
          },
        ],
        responseHeaders: [{ en_query_nextpage: "123" }],
      });
    });
    
    it("Should execute without response headers", async () => {
      const attrEnoFactory1 = new EnoFactory(
        "query/attribute",
        "security/policy/everyone"
      );
      attrEnoFactory1.setField("query/attribute/label", ["Attribute 1"]);
      attrEnoFactory1.setField("query/attribute/formula", ['FIELD("attr1")']);
      const attrEno1 = attrEnoFactory1.makeEno();
  
      const attrEnoFactory2 = new EnoFactory(
        "query/attribute",
        "security/policy/everyone"
      );
      attrEnoFactory2.setField("query/attribute/label", ["Attribute 2"]);
      attrEnoFactory2.setField("query/attribute/formula", ['FIELD("attr2")']);
      const attrEno2 = attrEnoFactory2.makeEno();
  
      spyOn(Send, "send").and.callFake((batch: Batch, options: IEnSrvOptions, queryOptions: IQueryOption) => {
        const responseEnoFactory = new EnoFactory("response/query");
        responseEnoFactory.setSecurity("security/policy/everyone");
        responseEnoFactory.setField("response/query/op-tip", [batch[0].tip]);
        responseEnoFactory.setField("response/query/attributes", [
          attrEno1.tip,
          attrEno2.tip,
        ]);
        responseEnoFactory.setField("response/query/result", [
          "value 1-1",
          "value 1-2",
          "value 1-R",
          "value 2-1",
          "value 2-2",
          "value 2-R",
        ]);
        responseEnoFactory.setField("response/query/exec-time", ["1"]);
        responseEnoFactory.setField("response/query/runtime-attributes", [
          JSON.stringify({ label: "runtimeAttr1", formula: "TIP()" }),
        ]);
        responseEnoFactory.setField("response/query/runtime-dimensions", [
          JSON.stringify({ label: "runtimeDim1", value: ["R1", "R2"] }),
        ]);
        const responseEno = responseEnoFactory.makeEno();
  
        return of([responseEno, attrEno1, attrEno2]);
      });
  
      const queryOptions: IQueryOption = {
        extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
        extraFilters: [{ label: "runtimeFilter1", formula: "TIP()" }],
        vars: {
          varKey1: ["varVal1a", "varVal1b"],
          varKey2: ["varVal2a", "varVal2b"],
        },
        dimensionOptions: [
          {
            label: "runtimeDim1",
            formula: "TIP()",
            sortby: ["TITLE()"],
            sortdir: ["asc"],
            limit: 1,
          },
        ],
      };
  
      const queryResponse = await firstValueFrom(
        execute1dWithResponseHeaders("test/query", testEnSrvOptions, queryOptions)
      );
  
      expect(queryResponse).toEqual([
        {
          "Attribute 1": "value 1-1",
          "Attribute 2": "value 1-2",
          runtimeAttr1: "value 1-R",
        },
        {
          "Attribute 1": "value 2-1",
          "Attribute 2": "value 2-2",
          runtimeAttr1: "value 2-R",
        },
      ]);
    });
  })
});
