# frozen_string_literal: true
require "spec_helper"

module SecureHeaders
  describe Cookie do
    let(:raw_cookie) { "_session=thisisatest" }

    it "does not tamper with cookies when using OPT_OUT is used" do
      cookie = Cookie.new(raw_cookie, OPT_OUT)
      expect(cookie.to_s).to eq(raw_cookie)
    end

    it "applies httponly, secure, and samesite by default" do
      cookie = Cookie.new(raw_cookie, nil)
      expect(cookie.to_s).to eq("_session=thisisatest; secure; HttpOnly; SameSite=Lax")
    end

    it "preserves existing attributes" do
      cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
      expect(cookie.to_s).to eq("_session=thisisatest; secure")
    end

    it "prevents duplicate flagging of attributes" do
      cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT)
      expect(cookie.to_s.scan(/secure/i).count).to eq(1)
    end

    context "Secure cookies" do
      context "when configured with a boolean" do
        it "flags cookies as Secure" do
          cookie = Cookie.new(raw_cookie, secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; secure")
        end
      end

      context "when configured with a Hash" do
        it "flags cookies as Secure when whitelisted" do
          cookie = Cookie.new(raw_cookie, secure: { only: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; secure")
        end

        it "does not flag cookies as Secure when excluded" do
          cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest")
        end
      end
    end

    context "HttpOnly cookies" do
      context "when configured with a boolean" do
        it "flags cookies as HttpOnly" do
          cookie = Cookie.new(raw_cookie, httponly: true, secure: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
        end
      end

      context "when configured with a Hash" do
        it "flags cookies as HttpOnly when whitelisted" do
          cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
        end

        it "does not flag cookies as HttpOnly when excluded" do
          cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest")
        end
      end
    end

    context "SameSite cookies" do
      %w(None Lax Strict).each do |flag|
        it "flags SameSite=#{flag}" do
          cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
        end

        it "flags SameSite=#{flag} when configured with a boolean" do
          cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true }, secure: OPT_OUT, httponly: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
        end

        it "does not flag cookies as SameSite=#{flag} when excluded" do
          cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
          expect(cookie.to_s).to eq("_session=thisisatest")
        end
      end

      it "flags SameSite=Strict when configured with a boolean" do
        cookie = Cookie.new(raw_cookie, { samesite: { strict: true }, secure: OPT_OUT, httponly: OPT_OUT })
        expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
      end

      it "flags properly when both lax and strict are configured" do
        raw_cookie = "_session=thisisatest"
        cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
        expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
      end

      it "ignores configuration if the cookie is already flagged" do
        raw_cookie = "_session=thisisatest; SameSite=Strict"
        cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
        expect(cookie.to_s).to eq(raw_cookie)
      end

      it "samesite: true sets all cookies to samesite=lax" do
        raw_cookie = "_session=thisisatest"
        cookie = Cookie.new(raw_cookie, samesite: true, secure: OPT_OUT, httponly: OPT_OUT)
        expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
      end
    end
  end

  context "with an invalid configuration" do
    it "raises an exception when not configured with a Hash" do
      expect do
        Cookie.validate_config!("configuration")
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when configured without a boolean(true or OPT_OUT)/Hash" do
      expect do
        Cookie.validate_config!(secure: "true")
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when configured with false" do
      expect do
        Cookie.validate_config!(secure: false)
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when both only and except filters are provided" do
      expect do
        Cookie.validate_config!(secure: { only: [], except: [] })
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when SameSite is not configured with a Hash" do
      expect do
        Cookie.validate_config!(samesite: true)
      end.to raise_error(CookiesConfigError)
    end

    cookie_options = %i(none lax strict)
    cookie_options.each do |flag|
      (cookie_options - [flag]).each do |other_flag|
        it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
          expect do
            Cookie.validate_config!(samesite: { flag => true, other_flag => true })
          end.to raise_error(CookiesConfigError)
        end
      end
    end

    it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
      expect do
        Cookie.validate_config!(samesite: { lax: true, strict: { only: ["_anything"] } })
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when both only and except filters are provided to SameSite configurations" do
      expect do
        Cookie.validate_config!(samesite: { lax: { only: ["_anything"], except: ["_anythingelse"] } })
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
      expect do
        Cookie.validate_config!(samesite: { lax: { only: ["_anything"] }, strict: { only: ["_anything"] } })
      end.to raise_error(CookiesConfigError)
    end

    it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
      expect do
        Cookie.validate_config!(samesite: { lax: { except: ["_anything"] }, strict: { except: ["_anything"] } })
      end.to raise_error(CookiesConfigError)
    end
  end
end
