"use strict";

const sinon = require("sinon");

describe("perpareToken", function () {
  let fakes, prepareToken;

  beforeEach(function () {
    fakes = {
      sign: sinon.stub(),
      resolve: sinon.stub(),
      decode: sinon.stub(),
    };

    prepareToken = require("../../../lib/credentials/token/prepare")(fakes);
  });

  const testOptions = {
    key: "key.pem",
    keyId: "123KeyId",
    teamId: "abcTeamId",
  };

  context("with valid options", function() {
    let token;

    beforeEach(function() {
      fakes.resolve.withArgs("key.pem").returns("keyData");
      fakes.sign.returns("generated-token");

      token = prepareToken(testOptions);
    });

    describe("return value", function (){

      describe("`current` property", function () {
        it("is initialized to a signed token", function () {
          expect(token.current).to.have.equal("generated-token");
        });
      });

      describe("`generation` property", function () {
        it("is initialized to 0", function () {
          expect(token.generation).to.equal(0);
        });
      });

      context("`regenerate` called with the current `generation` value", function () {
        let generation;

        beforeEach(function () {
          generation = Math.floor(Math.random() * 10) + 2;

          token.generation = generation;

          fakes.sign.reset();
          fakes.sign.onCall(0).returns("second-token");

          token.regenerate(generation);
        });

        it("increments `generation` property", function () {
          expect(token.generation).to.equal(generation + 1);
        });

        it("invokes the sign method with the correct arguments", function (){
          expect(fakes.sign).to.have.been.calledWith(
            sinon.match({}), // empty payload
            "keyData", 
            sinon.match({
              algorithm: "ES256",
              issuer: "abcTeamId",
              header: sinon.match({
                kid: "123KeyId",
              }),
            })
          );
        });

        it("updates the `current` property to the return value of the sign method", function () {
          expect(token.current).to.equal("second-token");
        });
      });

      context("`regenerate` called with a lower `generation` value", function () {
        let generation;

        beforeEach(function () {
          generation = Math.floor(Math.random() * 10) + 2;

          token.generation = generation;

          fakes.sign.reset();
          fakes.sign.onCall(0).returns("second-token");

          token.regenerate(generation - 1);
        });

        it("does not increment `generation` property", function () {
          expect(token.generation).to.equal(generation);
        });

        it("does not invoke the sign method", function () {
          expect(fakes.sign).to.have.not.been.called;
        });

        it("does not change the `current` property", function () {
          expect(token.current).to.equal("generated-token");
        });
      });

      context("`isExpired` called with expired token", function () {
        let token;
        beforeEach(function () {
          fakes.resolve.withArgs("key.pem").returns("keyData");
          fakes.decode.onCall(0).returns({iat:Math.floor(Date.now() / 1000)-1});
          token = prepareToken(testOptions);
        });

        it("token is not expired", function () {
          expect(token.isExpired(0)).to.equal(true);
        });
      });

      context("`isExpired` called with valid token", function () {
        let token;
        beforeEach(function () {
          fakes.resolve.withArgs("key.pem").returns("keyData");
          fakes.decode.onCall(0).returns({iat:Math.floor(Date.now() / 1000)});
          token = prepareToken(testOptions);
        });

        it("token is not expired", function () {
          expect(token.isExpired(5)).to.equal(false);
        });
      });
      
    });
  });

  context("with bad `key` parameter", function () {
    context("key resolution fails", function () {
      it("throws a wrapped error", function () {
        fakes.resolve.withArgs("key.pem").throws(new Error("ENOENT: Unable to read file key.pem"));

        expect(() => prepareToken(testOptions)).to.throw(/Failed loading token key: ENOENT: Unable to read file key.pem/);
      });
    });

    context("key cannot be used for signing", function () {
      it("throws a wrapped error from jwt.sign", function () {
        fakes.sign.throws(new Error("Unable to sign token"));

        expect(() => prepareToken(testOptions)).to.throw(/Failed to generate token: Unable to sign token/);
      });
    });
  });
});
