#include "ut_common.h"

#include <library/cpp/testing/unittest/registar.h>

using namespace NSrvKernel;
using namespace NSrvKernel::NCookie;

Y_UNIT_TEST_SUITE(TSetCookieTest) {

    Y_UNIT_TEST(TestAllAttrs) {
        TestParse(
            "a=b; Path=/; Domain=ya.ru; Expires=Wed, 20 May 2020 19:57:04 GMT; Max-Age=4; SameSite=None; Secure; HttpOnly",
            TSetCookie{
                .Name="a",
                .Value=FromStringBuf("b"),
                .Path="/",
                .Domain="ya.ru",
                .Expires=TExpires{.Year=2020, .Month=5, .MDay=20, .Hour=19, .Minute=57, .Second=4},
                .MaxAge=4,
                .SameSite=ESameSite::None,
                .Secure=true,
                .HttpOnly=true
            }
        );
        TestParse(
            "; Path=/; Domain=.yandex.ru; Expires=Wed, 20 May 2020 19:57:04 GMT; Max-Age=4; SameSite=None; Secure; HttpOnly; Path; Domain; Expires; Max-Age; SameSite",
            TSetCookieSyntaxErrors{
                ESetCookieSyntaxError::CookieEmpty,
                ESetCookieSyntaxError::PathValueInvalid,
                ESetCookieSyntaxError::PathValueConflict,
                ESetCookieSyntaxError::DomainValueInvalid,
                ESetCookieSyntaxError::DomainValueConflict,
                ESetCookieSyntaxError::ExpiresValueInvalid,
                ESetCookieSyntaxError::ExpiresValueConflict,
                ESetCookieSyntaxError::MaxAgeValueInvalid,
                ESetCookieSyntaxError::MaxAgeValueConflict,
                ESetCookieSyntaxError::SameSiteValueInvalid,
                ESetCookieSyntaxError::SameSiteValueConflict,
            }
        );
        TestParse(
            "; Path; Domain; Expires; Max-Age; SameSite; Path=/; Domain=.yandex.ru; Expires=Wed, 20 May 2020 19:57:04 GMT; Max-Age=4; SameSite=None; Secure; HttpOnly",
            TSetCookieSyntaxErrors{
                ESetCookieSyntaxError::CookieEmpty,
                ESetCookieSyntaxError::PathValueInvalid,
                ESetCookieSyntaxError::PathValueConflict,
                ESetCookieSyntaxError::DomainValueInvalid,
                ESetCookieSyntaxError::DomainValueConflict,
                ESetCookieSyntaxError::ExpiresValueInvalid,
                ESetCookieSyntaxError::ExpiresValueConflict,
                ESetCookieSyntaxError::MaxAgeValueInvalid,
                ESetCookieSyntaxError::MaxAgeValueConflict,
                ESetCookieSyntaxError::SameSiteValueInvalid,
                ESetCookieSyntaxError::SameSiteValueConflict,
            }
        );
    }

    Y_UNIT_TEST(TestPath) {
        TestParse(
            "a=; Path=/!@#$%^&*()-_+=,./?~`{}[]|\\",
            TSetCookie{.Name="a", .Path="/!@#$%^&*()-_+=,./?~`{}[]|\\"}
        );
        TestParse(
            "a=; Path=",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::PathValueInvalid});
        TestParse(
            "a=; Path=a",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::PathValueInvalid});
        TestParse(
            "a=; Path=/; path=/",
            TSetCookie{.Name="a", .Path="/"},
            "a=; Path=/"
        );
        TestParse(
            "a=; Path=/a; path=/b",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::PathValueConflict});
    }

    Y_UNIT_TEST(TestDomain) {
        TestParse(
            "a=; DOMAIN=.yandex.ru; domain=.yandex.ru",
            TSetCookie{.Name="a", .Domain=".yandex.ru"},
            "a=; Domain=.yandex.ru"
        );
        TestParse(
            "a=; DOMAIN=.yandex.ru; domain=any.yandex.ru",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::DomainValueConflict}
        );
        TestParse(
            "a=; Domain=ANY.YANDEX.RU",
            TSetCookie{.Name="a", .Domain="any.yandex.ru"},
            "a=; Domain=any.yandex.ru"
        );
        TestParse(
            "a=; Domain=",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::DomainValueInvalid}
        );
        TestParse("__Host-a=; Domain=",
            TSetCookie{.Name="__Host-a"}, "__Host-a=");
        TestParse(
            "a=; Domain=213.180.204.242",
            TSetCookie{.Name="a", .Domain="213.180.204.242"});
        TestParse(
            "a=; Domain=[2a02:6b8::242]",
            TSetCookie{.Name="a", .Domain="[2a02:6b8::242]"});
    }

    Y_UNIT_TEST(TestExpires) {
        // Testing the cached source input trick and the measurement
        {
            TSetCookie c{
                .Name="a",
                .Expires=TMaybeExpires{
                    FromStringBuf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
                    TExpires{.Year=2012, .Month=12, .MDay=21, .Hour=12, .Minute=12, .Second=12}
                },
            };
            UNIT_ASSERT_VALUES_EQUAL(
                ToStringBuf(c.Render()),
                "a=; Expires=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            );
            c.Expires = c.Expires.Get();
            UNIT_ASSERT_VALUES_EQUAL(
                ToStringBuf(c.Render()),
                "a=; Expires=Fri, 21 Dec 2012 12:12:12 GMT"
            );
        }
        TestParse(
            "a=; EXPIRES=Fri, 21 Dec 2012 12:12:12 GMT; expires=Fri, 21 Dec 2012 12:12:12 GMT; ",
            TSetCookie{
                .Name="a",
                .Expires=TExpires{.Year=2012, .Month=12, .MDay=21, .Hour=12, .Minute=12, .Second=12}
            },
            "a=; Expires=Fri, 21 Dec 2012 12:12:12 GMT"
        );
        TestParse(
            "a=; EXPIRES=Fri, 21 Dec 2012 12:12:12 GMT; expires=Fri, 21 Dec 2012 12:12:13 GMT; ",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::ExpiresValueConflict});
        TestParse(
            "a=; Expires=",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::ExpiresValueInvalid});
    }

    Y_UNIT_TEST(TestMaxAge) {
        TestParse(
            "a=; Max-Age=-1",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::MaxAgeValueInvalid}
        );
        TestParse(
            "a=; Max-Age=4294967296",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::MaxAgeValueInvalid}
        );
        TestParse(
            "a=; Max-Age=4294967295",
            TSetCookie{.Name="a", .MaxAge=Max<ui32>()}
        );
        TestParse(
            "a=; MAX-AGE=1; max-age=1",
            TSetCookie{.Name="a", .MaxAge=1},
            "a=; Max-Age=1"
        );
        TestParse(
            "a=; MAX-AGE=1; max-age=2",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::MaxAgeValueConflict}
        );
        TestParse(
            "a=; Max-Age=",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::MaxAgeValueInvalid}
        );
    }

    Y_UNIT_TEST(TestSameSite) {
        TestParse(
            "a=; SameSite=None",
            TSetCookie{.Name="a", .SameSite=ESameSite::None});
        TestParse(
            "a=; SameSite=Lax",
            TSetCookie{.Name="a", .SameSite=ESameSite::Lax});
        TestParse(
            "a=; SameSite=Strict",
            TSetCookie{.Name="a", .SameSite=ESameSite::Strict});
        TestParse(
            "a=; SAMESITE=STRICT; samesite=Strict",
            TSetCookie{.Name="a", .SameSite=ESameSite::Strict},
            "a=; SameSite=Strict"
        );
        TestParse(
            "a=; SAMESITE=STRICT; samesite=Lax",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::SameSiteValueConflict});
        TestParse(
            "a=; SameSite=",
            TSetCookieSyntaxErrors{ESetCookieSyntaxError::SameSiteValueInvalid});
    }

    Y_UNIT_TEST(TestSecure) {
        TestParse(
            "a=; SECURE=a; secure=b",
            TSetCookie{.Name="a", .Secure=true},
            "a=; Secure"
        );
        TestParse(
            "a=; SECURE",
            TSetCookie{.Name="a", .Secure=true},
            "a=; Secure"
        );
        TestParse(
            "a=; secure",
            TSetCookie{.Name="a", .Secure=true},
            "a=; Secure"
        );
    }

    Y_UNIT_TEST(TestHttpOnly) {
        TestParse(
            "a=; HttpOnly=a; httponly=b",
            TSetCookie{.Name="a", .HttpOnly=true},
            "a=; HttpOnly"
        );
    }

    void DoTestHasDeletion(TSetCookie c, TExpires now, bool hasDeletion) {
        UNIT_ASSERT_VALUES_EQUAL_C(
            HasDeletion(c, *now.ToInstant()), hasDeletion, NCookie::ToStringBuf(c.Render()));
        auto rendered = TString(NCookie::ToStringBuf(c.Render()));
        TestParse(rendered, c, Nothing());
    }

    Y_UNIT_TEST(TestHasDeletion) {
        auto prev = TExpires{.Year=2020, .Month=7, .MDay=15, .Hour=12, .Minute=13, .Second=13};
        auto curr = TExpires{.Year=2020, .Month=7, .MDay=15, .Hour=12, .Minute=13, .Second=14};
        DoTestHasDeletion({.Name="a", .Expires=curr}, curr, false);
        DoTestHasDeletion({.Name="a", .Expires=curr}, prev, false);
        DoTestHasDeletion({.Name="a", .Expires=prev}, curr, true);
        DoTestHasDeletion({.Name="a", .MaxAge=1}, curr, false);
        DoTestHasDeletion({.Name="a", .MaxAge=0}, curr, true);
        DoTestHasDeletion({.Name="a", .Expires=curr, .MaxAge=1}, curr, false);
        DoTestHasDeletion({.Name="a", .Expires=curr, .MaxAge=1}, prev, false);
        DoTestHasDeletion({.Name="a", .Expires=prev, .MaxAge=0}, curr, true);
        DoTestHasDeletion({.Name="a", .Expires=prev, .MaxAge=1}, curr, false);
        DoTestHasDeletion({.Name="a", .Expires=curr, .MaxAge=0}, curr, true);
        DoTestHasDeletion({.Name="a", .Expires=curr, .MaxAge=0}, prev, true);
    }
}
