Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "gtest/gtest.h"
#include "mozilla/gtest/MozAssertions.h"
#include "mozilla/net/NoVarySearchUtils.h"
#include "nsNetUtil.h"
using namespace mozilla::net;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static NoVarySearchData Parse(const char* aHeader) {
return ParseNoVarySearchHeader(nsDependentCString(aHeader));
}
static nsCOMPtr<nsIURI> MakeURI(const char* aSpec) {
nsCOMPtr<nsIURI> uri;
NS_NewURI(getter_AddRefs(uri), nsDependentCString(aSpec));
return uri;
}
static bool Equiv(const char* aURIA, const char* aURIB, const char* aHeader) {
auto data = Parse(aHeader);
nsCOMPtr<nsIURI> uriA = MakeURI(aURIA);
nsCOMPtr<nsIURI> uriB = MakeURI(aURIB);
return URLsAreEquivalentModuloVariationConfig(uriA, uriB, data);
}
// ---------------------------------------------------------------------------
// ParseNoVarySearchHeader tests
// ---------------------------------------------------------------------------
TEST(TestNoVarySearchParse, EmptyHeader)
{
auto d = Parse("");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
EXPECT_TRUE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, InvalidSF)
{
auto d = Parse("!!!not valid sf!!!");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, ParamsInvalidType)
{
// params with an integer value is not a valid type → ExactMatch
auto d = Parse("params=42");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, UnknownKeysOnly)
{
// Header with only unknown keys → ExactMatch (no params, no except)
auto d = Parse("x-custom=?1, x-other=?0");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
EXPECT_TRUE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, ParamsAndExceptTogether)
{
// params=(inner list) + except → invalid → ExactMatch
auto d = Parse(R"(params=("a"), except=("b"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, ExceptWithoutParams)
{
// except without params → invalid → ExactMatch
auto d = Parse(R"(except=("a"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, ExceptEmptyWithoutParams)
{
// except with empty inner list, no params → invalid → ExactMatch
auto d = Parse("except=()");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, ParamsBareToken)
{
// bare `params` token → IgnoreAll
auto d = Parse("params");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::IgnoreAll);
EXPECT_TRUE(d.paramNames.IsEmpty());
EXPECT_TRUE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, ParamsExplicitBooleanTrue)
{
// params=?1 (explicit boolean true) → IgnoreAll, same as bare params
auto d = Parse("params=?1");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::IgnoreAll);
EXPECT_TRUE(d.paramNames.IsEmpty());
}
TEST(TestNoVarySearchParse, ParamsExplicitBooleanFalse)
{
// params=?0 (boolean false) → ExactMatch (not IgnoreAll)
auto d = Parse("params=?0");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
}
TEST(TestNoVarySearchParse, ParamsSingleItem)
{
auto d = Parse(R"(params=("a"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Blocklist);
ASSERT_EQ(d.paramNames.Length(), 1u);
EXPECT_EQ(d.paramNames[0], "a"_ns);
}
TEST(TestNoVarySearchParse, ParamsMultipleItems)
{
auto d = Parse(R"(params=("a" "b" "c"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Blocklist);
ASSERT_EQ(d.paramNames.Length(), 3u);
EXPECT_EQ(d.paramNames[0], "a"_ns);
EXPECT_EQ(d.paramNames[1], "b"_ns);
EXPECT_EQ(d.paramNames[2], "c"_ns);
}
TEST(TestNoVarySearchParse, ExceptAllowlist)
{
// params, except=("id") → Allowlist
auto d = Parse(R"(params, except=("id"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Allowlist);
ASSERT_EQ(d.paramNames.Length(), 1u);
EXPECT_EQ(d.paramNames[0], "id"_ns);
}
TEST(TestNoVarySearchParse, ExceptAllowlistExplicitBoolTrue)
{
// params=?1 + except=(...) → Allowlist (same as bare params + except)
auto d = Parse(R"(params=?1, except=("id"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Allowlist);
ASSERT_EQ(d.paramNames.Length(), 1u);
EXPECT_EQ(d.paramNames[0], "id"_ns);
}
TEST(TestNoVarySearchParse, ExceptMultipleKeys)
{
auto d = Parse(R"(params, except=("id" "type"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Allowlist);
ASSERT_EQ(d.paramNames.Length(), 2u);
}
TEST(TestNoVarySearchParse, KeyOrderTrue)
{
// bare key-order → varyOnKeyOrder=false
auto d = Parse("key-order");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::ExactMatch);
EXPECT_FALSE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, KeyOrderExplicitTrue)
{
// key-order=?1 (explicit boolean true) → varyOnKeyOrder=false
auto d = Parse("key-order=?1");
EXPECT_FALSE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, KeyOrderExplicitFalse)
{
// key-order=?0 → varyOnKeyOrder=true (order matters, explicit default)
auto d = Parse("key-order=?0");
EXPECT_TRUE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, KeyOrderCombinedWithParams)
{
auto d = Parse(R"(params=("utm"), key-order)");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Blocklist);
ASSERT_EQ(d.paramNames.Length(), 1u);
EXPECT_EQ(d.paramNames[0], "utm"_ns);
EXPECT_FALSE(d.varyOnKeyOrder);
}
TEST(TestNoVarySearchParse, SFExtensionParamsOnItemsIgnored)
{
// Unknown SF parameters on items must be ignored
auto d = Parse(R"(params=("a";unknown="x"))");
EXPECT_EQ(d.paramsRule, NoVarySearchData::ParamsRule::Blocklist);
ASSERT_EQ(d.paramNames.Length(), 1u);
EXPECT_EQ(d.paramNames[0], "a"_ns);
}
// ---------------------------------------------------------------------------
// URLsAreEquivalentModuloVariationConfig — parametrized tests
// ---------------------------------------------------------------------------
struct EquivTestData {
const char* description;
const char* urlA;
const char* urlB;
const char* header;
bool expected;
};
static const EquivTestData kEquivTests[] = {
// ExactMatch (no header)
// Structural mismatches always return false regardless of NVS
// Blocklist: params=("a")
{"Blocklist: non-listed param differs → MISS",
R"(params=("a"))", false},
// Blocklist: many params in query string, only some in NVS list
{"Blocklist: many params, only listed ones ignored → HIT",
{"Blocklist: many params, non-listed param differs → MISS",
false},
// Blocklist: params in query don't match any NVS listed params
{"Blocklist: no query params match NVS list → treated as ExactMatch",
R"(params=("a" "b"))", false},
{"Blocklist: no query params match NVS list, identical → HIT",
R"(params=("a" "b"))", true},
// IgnoreAll: params
{"IgnoreAll: completely different params → HIT",
true},
// Allowlist: params, except=("id")
{"Allowlist: kept param same → HIT",
{"Allowlist: params=?1 + except, kept param same → HIT",
true},
// key-order
{"key-order: reordered but value differs → MISS",
"key-order", false},
// Combined
{"Combined params+key-order: ignored param and order both differ → HIT",
true},
// Invalid SF → ExactMatch fallback
{"Invalid SF (params inner list + except) → ExactMatch → MISS",
R"(params=("a"), except=("b"))", false},
// multi-value params
{"key-order: multi-value, same values reordered by key → HIT",
"key-order", true},
{"key-order: multi-value, values for same key in different order → MISS",
"key-order", false},
};
class TestNoVarySearchEquivalence
: public testing::TestWithParam<EquivTestData> {};
TEST_P(TestNoVarySearchEquivalence, Check) {
const EquivTestData& t = GetParam();
EXPECT_EQ(Equiv(t.urlA, t.urlB, t.header), t.expected) << t.description;
// Equivalence must be symmetric
EXPECT_EQ(Equiv(t.urlB, t.urlA, t.header), t.expected)
<< t.description << " (symmetric)";
}
INSTANTIATE_TEST_SUITE_P(TestNoVarySearch, TestNoVarySearchEquivalence,
testing::ValuesIn(kEquivTests));