Skip to content

dev_codec_cookie_test_vectors.erl - Cookie Codec Test Vectors

Overview

Purpose: Comprehensive test suite for cookie parsing and encoding correctness
Module: dev_codec_cookie_test_vectors
Test Type: Property-based equivalence testing
Coverage: Cookie parsing, serialization, attributes, flags

This module provides a battery of test vectors for validating the dev_codec_cookie codec implementation. It uses equivalence classes to test that various input formats all produce the same normalized output, ensuring robust parsing and encoding.

Dependencies

  • Testing: eunit
  • Codecs: dev_codec_cookie
  • HyperBEAM: hb_maps
  • Includes: include/hb.hrl

Internal Functions Overview

This module contains only internal (non-exported) functions used for testing:

%% Test Utilities (internal)
test_data() -> TestDataMap.
assert_set(TestSetName, Function) -> ok.
to_string(CookieMsg) -> SetCookieStrings.
from_string(String) -> CookieMsg.
 
%% EUnit Tests (internal)
from_string_basic_test() -> ok.
from_string_attributes_test() -> ok.
from_string_flags_test() -> ok.
to_string_basic_test() -> ok.
to_string_attributes_test() -> ok.
to_string_flags_test() -> ok.
%% ... and many more test functions

Internal Functions

1. test_data/0

test_data() -> TestDataMap
    when
        TestDataMap :: #{
            TestSetName => {[Input], ExpectedOutput}
        }.

Description: Returns a map of test sets where each entry contains a list of equivalent inputs and their expected normalized output. All inputs should produce the same output when processed.

Test Sets Included:
  • from_string_raw_value - Simple key-value cookie parsing
  • from_string_attributes - Cookie attributes parsing
  • from_string_flags - Cookie flags parsing
  • to_string_raw_value - Cookie serialization with raw values
  • to_string_attributes - Cookie serialization with attributes
  • to_string_flags - Cookie serialization with flags
  • Real-world test cases for various cookie scenarios
Test Code:
-module(test_dev_codec_cookie_test_vectors).
-include_lib("eunit/include/eunit.hrl").
 
cookie_parse_simple_test() ->
    % Test basic cookie parsing via dev_codec_cookie
    {ok, Msg} = dev_codec_cookie:from(
        #{ <<"set-cookie">> => <<"k1=v1">> },
        #{},
        #{}
    ),
    {ok, Cookie} = dev_codec_cookie:extract(Msg, #{}, #{}),
    ?assertEqual(<<"v1">>, maps:get(<<"k1">>, Cookie)).
 
cookie_parse_quoted_test() ->
    % Test quoted cookie value parsing
    {ok, Msg} = dev_codec_cookie:from(
        #{ <<"set-cookie">> => <<"k1=\"v1\"">> },
        #{},
        #{}
    ),
    {ok, Cookie} = dev_codec_cookie:extract(Msg, #{}, #{}),
    ?assertEqual(<<"v1">>, maps:get(<<"k1">>, Cookie)).

2. assert_set/2

assert_set(TestSet, Fun) -> ok
    when
        TestSet :: atom(),
        Fun :: fun((Input) -> Output).

Description: Assert that when given the inputs in the test set, the outputs are all equal to the expected value when the function is applied to them. This implements equivalence class testing.

Test Code:
cookie_parse_attributes_test() ->
    % Test cookie with attributes parsing
    {ok, Msg} = dev_codec_cookie:from(
        #{ <<"set-cookie">> => <<"k1=v1; Path=/">> },
        #{},
        #{}
    ),
    {ok, Cookie} = dev_codec_cookie:extract(Msg, #{}, #{}),
    K1 = maps:get(<<"k1">>, Cookie),
    ?assert(is_map(K1)),
    ?assertEqual(<<"v1">>, maps:get(<<"value">>, K1)).
 
cookie_parse_flags_test() ->
    % Test cookie with flags parsing
    {ok, Msg} = dev_codec_cookie:from(
        #{ <<"set-cookie">> => <<"k1=v1; Secure; HttpOnly">> },
        #{},
        #{}
    ),
    {ok, Cookie} = dev_codec_cookie:extract(Msg, #{}, #{}),
    K1 = maps:get(<<"k1">>, Cookie),
    ?assert(maps:is_key(<<"flags">>, K1)).

3. to_string/1

to_string(CookieMsg) -> SetCookieStrings
    when
        CookieMsg :: map(),
        SetCookieStrings :: [binary()].

Description: Convert a cookie message to a list of Set-Cookie header strings. Uses dev_codec_cookie:store/3 and dev_codec_cookie:to/3 internally.

Test Code:
cookie_to_string_test() ->
    Opts = hb_private:opts(#{}),
    Msg = hb_private:set(#{}, <<"cookie">>, #{
        <<"k1">> => <<"v1">>
    }, Opts),
    {ok, Result} = dev_codec_cookie:to(Msg, #{ <<"format">> => <<"set-cookie">> }, #{}),
    SetCookies = maps:get(<<"set-cookie">>, Result),
    ?assert(is_list(SetCookies)),
    ?assertEqual(1, length(SetCookies)).
 
cookie_to_cookie_format_test() ->
    Opts = hb_private:opts(#{}),
    Msg = hb_private:set(#{}, <<"cookie">>, #{
        <<"k1">> => <<"v1">>,
        <<"k2">> => <<"v2">>
    }, Opts),
    {ok, Result} = dev_codec_cookie:to(Msg, #{ <<"format">> => <<"cookie">> }, #{}),
    Cookie = maps:get(<<"cookie">>, Result),
    ?assert(is_binary(Cookie)).

4. from_string/1

from_string(String) -> CookieMsg
    when
        String :: binary() | [binary()],
        CookieMsg :: map().

Description: Convert a Set-Cookie header string (or list of strings) into a cookie message map. Uses dev_codec_cookie:from/3 and dev_codec_cookie:extract/3 internally.

Test Code:
cookie_from_header_test() ->
    Msg = #{ <<"cookie">> => <<"k1=v1; k2=v2">> },
    {ok, Parsed} = dev_codec_cookie:from(Msg, #{}, #{}),
    {ok, Cookies} = dev_codec_cookie:extract(Parsed, #{}, #{}),
    ?assertEqual(<<"v1">>, maps:get(<<"k1">>, Cookies)),
    ?assertEqual(<<"v2">>, maps:get(<<"k2">>, Cookies)).
 
cookie_from_set_cookie_test() ->
    Msg = #{ <<"set-cookie">> => <<"session=abc123; Path=/">> },
    {ok, Parsed} = dev_codec_cookie:from(Msg, #{}, #{}),
    {ok, Cookies} = dev_codec_cookie:extract(Parsed, #{}, #{}),
    Session = maps:get(<<"session">>, Cookies),
    ?assertEqual(<<"abc123">>, maps:get(<<"value">>, Session)).

Test Vector Categories

1. Raw Value Tests

from_string_raw_value =>
    {
        % All these inputs should parse to the same output
        [<<"k1=v1">>, <<"k1=\"v1\"">>],
        #{<<"k1">> => <<"v1">>}
    }
 
to_string_raw_value =>
    {
        % All these should serialize to the same output
        [
            #{<<"k1">> => <<"v1">>},
            #{<<"k1">> => #{<<"value">> => <<"v1">>}},
            #{<<"k1">> => #{
                <<"value">> => <<"v1">>,
                <<"attributes">> => #{},
                <<"flags">> => []
            }}
        ],
        [<<"k1=\"v1\"">>]
    }

2. Attribute Tests

from_string_attributes =>
    {
        [<<"k1=v1; k2=v2">>, <<"k1=\"v1\"; k2=\"v2\"">>],
        #{
            <<"k1">> => #{
                <<"value">> => <<"v1">>,
                <<"attributes">> => #{<<"k2">> => <<"v2">>}
            }
        }
    }

3. Flag Tests

from_string_flags =>
    {
        [<<"k1=v1; k2=v2; f1; f2">>, <<"k1=\"v1\"; k2=\"v2\"; f1; f2">>],
        #{
            <<"k1">> => #{
                <<"value">> => <<"v1">>,
                <<"attributes">> => #{<<"k2">> => <<"v2">>},
                <<"flags">> => [<<"f1">>, <<"f2">>]
            }
        }
    }

Testing Strategy

1. Equivalence Class Testing

Test that various input formats parse to the same normalized structure:

% Both quoted and unquoted values parse identically
<<"k1=v1">> ≡ <<"k1=\"v1\"">>
% Both parse to: #{<<"k1">> => <<"v1">>}

2. Round-trip Testing

Test that parse → serialize → parse produces identical results.

3. Real-world Scenarios

Tests include real-world cookie patterns:

  • Session cookies with SameSite attributes
  • JWT tokens in cookies
  • Multi-value cookies
  • Cookies with special characters

References

  • Cookie Codec - dev_codec_cookie.erl
  • HTTP Standards - RFC 6265 (HTTP State Management Mechanism)
  • EUnit Testing - Erlang unit testing framework

Notes

  1. Test Module: This is a pure test module with no public exports
  2. Equivalence Testing: Uses equivalence classes to test parsing robustness
  3. Normalization: All equivalent inputs normalize to same structure
  4. Quote Handling: Handles both quoted and unquoted cookie values
  5. Attribute Support: Tests various cookie attributes (Path, Domain, etc.)
  6. Flag Support: Tests boolean flags (Secure, HttpOnly, SameSite)
  7. Coverage: Comprehensive test coverage of cookie codec functionality