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

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

  @key <<253, 57, 133, 213, 217, 187, 91, 8, 237, 166, 48, 95, 66, 137, 244, 65, 124, 23, 58, 234,
         100, 194, 212, 109, 22, 49, 56, 145, 147, 95, 138, 14>>

  # 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 <<65, 69, 83, 46, 67, 84, 82, 86, 49, 201, 160, 52, 214, 196, 165, 118, 203,
                       13, 36, 248, 198, 51, 154, 225, 233, 98, 187, 166, 119, 6, 42, 103, 41,
                       208, 50, 216, 95, 99, 116, 45, 155, 179, 96, 21, 151, 83, 145, 106, 102,
                       47, 237>>

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

  describe ".encrypt/2" do
    test "raises error, preventing use" do
      assert_raise RuntimeError, fn ->
        Cipher.encrypt("plaintext", @opts)
      end
    end
  end

  describe ".decrypt/2" do
    setup :create_ciphertext

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

    test "returns error if :module_tag does not match", %{ciphertext: ciphertext} do
      assert :error == Cipher.decrypt(ciphertext, Keyword.merge(@opts, module_tag: "Other"))
    end

    test "returns error if :tag does not match", %{ciphertext: ciphertext} do
      assert :error == Cipher.decrypt(ciphertext, Keyword.merge(@opts, tag: "Other"))
    end

    test "returns error on invalid ciphertext" do
      assert :error == Cipher.decrypt(<<0, 1>>, @opts)
    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 opts match", %{ciphertext: ciphertext} do
      assert Cipher.can_decrypt?(ciphertext, @opts)
    end

    test "returns false if opts don't match", %{ciphertext: ciphertext} do
      refute Cipher.can_decrypt?(ciphertext, Keyword.merge(@opts, module_tag: "Other"))
      refute Cipher.can_decrypt?(ciphertext, Keyword.merge(@opts, tag: "Other"))
    end
  end

  # Replicates the old format for AES.CTR, where the module tag
  # and key tag were prepended to the iv and ciphertext.
  defp create_ciphertext(_) do
    iv = :crypto.strong_rand_bytes(16)
    ciphertext = Cloak.Crypto.encrypt_one_time(:aes_256_ctr, @opts[:key], iv, "plaintext")
    [ciphertext: @opts[:module_tag] <> @opts[:tag] <> iv <> ciphertext]
  end
end
