defmodule Cloak.Ciphers.AES.CTRTest do
  use ExUnit.Case

  alias Cloak.Ciphers.AES.CTR, as: Cipher

  @key <<222, 200, 232, 93, 253, 166, 8, 129, 248, 254, 131, 204, 238, 46, 135, 93, 199, 246, 164,
         128, 56, 92, 188, 64, 46, 123, 150, 130, 235, 234, 46, 199>>

  # This hard-coded ciphertext is used for regression tests to ensure
  # that future versions of Cloak can still decrypt ciphertexts generated
  # by v1.0.3, prior to the :crypto changes in Erlang 24
  @v1_0_3_ciphertext <<1, 10, 65, 69, 83, 46, 67, 84, 82, 46, 86, 49, 215, 241, 218, 168, 171,
                       131, 241, 194, 29, 20, 242, 139, 22, 28, 244, 132, 35, 206, 170, 207, 166,
                       151, 66, 157, 111, 150, 221, 102, 89, 233, 60, 86, 160, 166, 210, 28, 56,
                       170, 5, 114, 146, 18>>

  @opts [tag: "AES.CTR.V1", key: @key]

  describe ".encrypt/2" do
    test "encrypts plaintext" do
      assert {:ok, ciphertext} = Cipher.encrypt("plaintext", @opts)
      assert ciphertext != "plaintext"
    end

    test "produces ciphertext in the format tag <> iv <> ciphertext" do
      assert {:ok,
              <<_type::binary-1, _length::binary-1, "AES.CTR.V1", iv::binary-16,
                ciphertext::binary>>} = Cipher.encrypt("plaintext", @opts)

      assert byte_size(iv) == 16
      assert String.length(ciphertext) > 0
    end

    test "produces a different ciphertext each time" do
      assert {:ok, ciphertext1} = Cipher.encrypt("plaintext", @opts)
      assert {:ok, ciphertext2} = Cipher.encrypt("plaintext", @opts)
      assert ciphertext1 != ciphertext2
    end

    test "raises error if :key not passed" do
      assert_raise KeyError, fn ->
        Cipher.encrypt("plaintext", tag: @opts[:tag])
      end
    end

    test "raises error if :tag not passed" do
      assert_raise KeyError, fn ->
        Cipher.encrypt("plaintext", key: @opts[:key])
      end
    end
  end

  describe ".decrypt/2" do
    setup :create_ciphertext

    test "decrypts ciphertext", %{ciphertext: ciphertext} do
      assert {:ok, plaintext} = Cipher.decrypt(ciphertext, @opts)
      assert plaintext == "plaintext"
    end

    test "returns error if given invalid ciphertext" do
      assert :error == Cipher.decrypt(<<0, 1>>, @opts)
    end

    test "raises error if :key not passed", %{ciphertext: ciphertext} do
      assert_raise KeyError, fn ->
        Cipher.decrypt(ciphertext, tag: @opts[:tag])
      end
    end

    test "raises error if :tag not passed", %{ciphertext: ciphertext} do
      assert_raise KeyError, fn ->
        Cipher.decrypt(ciphertext, key: @opts[:key])
      end
    end

    test "can decrypt a ciphertext generated by Cloak v1.0.3" do
      assert Cipher.decrypt(@v1_0_3_ciphertext, @opts) == {:ok, "Lorem ipsum dolor sit amet"}
    end
  end

  describe ".can_decrypt?/2" do
    setup :create_ciphertext

    test "returns true if tag and format matches", %{ciphertext: ciphertext} do
      assert Cipher.can_decrypt?(ciphertext, @opts)
    end

    test "returns false if tag does not match", %{ciphertext: ciphertext} do
      refute Cipher.can_decrypt?(ciphertext, tag: "OtherTag", key: @opts[:key])
    end

    test "returns false if tag matches but format does not" do
      refute Cipher.can_decrypt?(<<1, 10, "AES.CTR.V1", 0, 0>>, @opts)
    end
  end

  defp create_ciphertext(_) do
    {:ok, ciphertext} = Cipher.encrypt("plaintext", @opts)
    [ciphertext: ciphertext]
  end
end
