detrack 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- detrack-0.1.0/LICENSE +21 -0
- detrack-0.1.0/PKG-INFO +27 -0
- detrack-0.1.0/README.md +2 -0
- detrack-0.1.0/pyproject.toml +41 -0
- detrack-0.1.0/setup.cfg +4 -0
- detrack-0.1.0/src/detrack/__init__.py +4 -0
- detrack-0.1.0/src/detrack/core.py +69 -0
- detrack-0.1.0/src/detrack/patterns.py +24 -0
- detrack-0.1.0/src/detrack.egg-info/PKG-INFO +27 -0
- detrack-0.1.0/src/detrack.egg-info/SOURCES.txt +14 -0
- detrack-0.1.0/src/detrack.egg-info/dependency_links.txt +1 -0
- detrack-0.1.0/src/detrack.egg-info/requires.txt +3 -0
- detrack-0.1.0/src/detrack.egg-info/top_level.txt +1 -0
- detrack-0.1.0/tests/test_core.py +86 -0
- detrack-0.1.0/tests/test_edge_cases.py +85 -0
- detrack-0.1.0/tests/test_patterns.py +61 -0
detrack-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emiliano Gandini Outeda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
detrack-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: detrack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Strip tracking parameters from URLs. Deterministically. Zero dependencies.
|
|
5
|
+
Author-email: Emiliano Gandini Outeda <emiliano.gandini@protonmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/emiliano-gandini-outeda/detrack
|
|
8
|
+
Keywords: seo,tracking,privacy,utm,url-cleaner
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# detrack
|
|
27
|
+
Strip tracking parameters from URLs. Deterministically. Zero dependencies.
|
detrack-0.1.0/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "detrack"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Strip tracking parameters from URLs. Deterministically. Zero dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Emiliano Gandini Outeda", email = "emiliano.gandini@protonmail.com" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
keywords = ["seo", "tracking", "privacy", "utm", "url-cleaner"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
test = ["pytest"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Source = "https://github.com/emiliano-gandini-outeda/detrack"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
package-dir = { "" = "src" }
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
detrack-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
3
|
+
from urllib.parse import SplitResult
|
|
4
|
+
|
|
5
|
+
from detrack.patterns import DEFAULT_PATTERNS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class DetrackResult:
|
|
10
|
+
url: str
|
|
11
|
+
parsed_url: SplitResult
|
|
12
|
+
cleaned_params: dict[str, str]
|
|
13
|
+
removed_params: list[str]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _filter_pairs(
|
|
17
|
+
query: str,
|
|
18
|
+
patterns: list[str] | None = None,
|
|
19
|
+
) -> tuple[list[tuple[str, str]], list[str]]:
|
|
20
|
+
patterns_set = frozenset(p.lower() for p in (patterns or list(DEFAULT_PATTERNS)))
|
|
21
|
+
pairs = parse_qsl(query, keep_blank_values=True)
|
|
22
|
+
cleaned: list[tuple[str, str]] = []
|
|
23
|
+
removed: list[str] = []
|
|
24
|
+
for key, val in pairs:
|
|
25
|
+
if key.lower() in patterns_set:
|
|
26
|
+
removed.append(key)
|
|
27
|
+
else:
|
|
28
|
+
cleaned.append((key, val))
|
|
29
|
+
return cleaned, removed
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def clean_query(query: str, patterns: list[str] | None = None) -> str:
|
|
33
|
+
cleaned, _ = _filter_pairs(query, patterns)
|
|
34
|
+
return urlencode(cleaned, doseq=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def clean(url: str, patterns: list[str] | None = None) -> DetrackResult:
|
|
38
|
+
try:
|
|
39
|
+
parsed = urlsplit(url)
|
|
40
|
+
if not parsed.query:
|
|
41
|
+
return DetrackResult(
|
|
42
|
+
url=url,
|
|
43
|
+
parsed_url=parsed,
|
|
44
|
+
cleaned_params={},
|
|
45
|
+
removed_params=[],
|
|
46
|
+
)
|
|
47
|
+
cleaned_pairs, removed = _filter_pairs(parsed.query, patterns)
|
|
48
|
+
cleaned_qs = urlencode(cleaned_pairs, doseq=True)
|
|
49
|
+
cleaned_dict = dict(cleaned_pairs)
|
|
50
|
+
new_url = urlunsplit((
|
|
51
|
+
parsed.scheme,
|
|
52
|
+
parsed.netloc,
|
|
53
|
+
parsed.path,
|
|
54
|
+
cleaned_qs,
|
|
55
|
+
parsed.fragment,
|
|
56
|
+
))
|
|
57
|
+
return DetrackResult(
|
|
58
|
+
url=new_url,
|
|
59
|
+
parsed_url=parsed,
|
|
60
|
+
cleaned_params=cleaned_dict,
|
|
61
|
+
removed_params=removed,
|
|
62
|
+
)
|
|
63
|
+
except Exception:
|
|
64
|
+
return DetrackResult(
|
|
65
|
+
url=url,
|
|
66
|
+
parsed_url=urlsplit(""),
|
|
67
|
+
cleaned_params={},
|
|
68
|
+
removed_params=[],
|
|
69
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
DEFAULT_PATTERNS = frozenset({
|
|
2
|
+
# UTM
|
|
3
|
+
"utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content",
|
|
4
|
+
"utm_id", "utm_cid", "utm_reader", "utm_viz", "utm_pubreferrer",
|
|
5
|
+
"utm_nooverride",
|
|
6
|
+
# Social
|
|
7
|
+
"fbclid", "ref", "source", "si", "sns", "social", "origin",
|
|
8
|
+
# Marketing / Ad
|
|
9
|
+
"gclid", "gclsrc", "dclid", "msclkid", "wbraid", "gbraid",
|
|
10
|
+
"mc_cid", "mc_eid", "trk", "vero_conv", "vero_id",
|
|
11
|
+
"email", "recipient", "campaign_id", "newsletter", "mbid",
|
|
12
|
+
# Analytics
|
|
13
|
+
"_ga", "_gl", "_ke", "_hsenc",
|
|
14
|
+
# Cache busters
|
|
15
|
+
"_", "cb", "cache", "nocache", "rand", "random", "r", "ts", "_t",
|
|
16
|
+
"timestamp",
|
|
17
|
+
# Session / visit
|
|
18
|
+
"session_id", "sid", "phpsessid", "jsessionid", "view_id", "visit_id",
|
|
19
|
+
# Redirect
|
|
20
|
+
"redirect_to", "return_to", "next", "continue",
|
|
21
|
+
# Misc / platform-specific
|
|
22
|
+
"yclid", "igshid", "_openstat", "oft_id", "wt_mc", "mtm_source",
|
|
23
|
+
"mtm_medium", "mtm_campaign", "mtm_keyword", "mtm_content",
|
|
24
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: detrack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Strip tracking parameters from URLs. Deterministically. Zero dependencies.
|
|
5
|
+
Author-email: Emiliano Gandini Outeda <emiliano.gandini@protonmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/emiliano-gandini-outeda/detrack
|
|
8
|
+
Keywords: seo,tracking,privacy,utm,url-cleaner
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: pytest; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# detrack
|
|
27
|
+
Strip tracking parameters from URLs. Deterministically. Zero dependencies.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/detrack/__init__.py
|
|
5
|
+
src/detrack/core.py
|
|
6
|
+
src/detrack/patterns.py
|
|
7
|
+
src/detrack.egg-info/PKG-INFO
|
|
8
|
+
src/detrack.egg-info/SOURCES.txt
|
|
9
|
+
src/detrack.egg-info/dependency_links.txt
|
|
10
|
+
src/detrack.egg-info/requires.txt
|
|
11
|
+
src/detrack.egg-info/top_level.txt
|
|
12
|
+
tests/test_core.py
|
|
13
|
+
tests/test_edge_cases.py
|
|
14
|
+
tests/test_patterns.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
detrack
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from detrack import DetrackResult, clean, clean_query
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_clean_basic() -> None:
|
|
5
|
+
result = clean("https://example.com/post?utm_source=twitter&q=python")
|
|
6
|
+
assert result.url == "https://example.com/post?q=python"
|
|
7
|
+
assert result.cleaned_params == {"q": "python"}
|
|
8
|
+
assert result.removed_params == ["utm_source"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_clean_multiple_trackers() -> None:
|
|
12
|
+
result = clean("https://example.com?a=1&utm_source=x&b=2&fbclid=y&c=3")
|
|
13
|
+
assert result.url == "https://example.com?a=1&b=2&c=3"
|
|
14
|
+
assert result.cleaned_params == {"a": "1", "b": "2", "c": "3"}
|
|
15
|
+
assert sorted(result.removed_params) == sorted(["utm_source", "fbclid"])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_clean_all_stripped() -> None:
|
|
19
|
+
result = clean("https://example.com?utm_source=x&fbclid=y")
|
|
20
|
+
assert result.url == "https://example.com"
|
|
21
|
+
assert result.cleaned_params == {}
|
|
22
|
+
assert sorted(result.removed_params) == sorted(["utm_source", "fbclid"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_clean_custom_patterns() -> None:
|
|
26
|
+
result = clean("https://example.com?session=abc123&page=1", patterns=["session"])
|
|
27
|
+
assert result.url == "https://example.com?page=1"
|
|
28
|
+
assert result.cleaned_params == {"page": "1"}
|
|
29
|
+
assert result.removed_params == ["session"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_clean_preserves_fragment() -> None:
|
|
33
|
+
result = clean("https://example.com/page?utm_source=x#section")
|
|
34
|
+
assert result.url == "https://example.com/page#section"
|
|
35
|
+
assert result.removed_params == ["utm_source"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_clean_no_query() -> None:
|
|
39
|
+
result = clean("https://example.com/page")
|
|
40
|
+
assert result.url == "https://example.com/page"
|
|
41
|
+
assert result.cleaned_params == {}
|
|
42
|
+
assert result.removed_params == []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_clean_empty_url() -> None:
|
|
46
|
+
result = clean("")
|
|
47
|
+
assert result.url == ""
|
|
48
|
+
assert result.cleaned_params == {}
|
|
49
|
+
assert result.removed_params == []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_clean_malformed_url() -> None:
|
|
53
|
+
result = clean("http:///example.com")
|
|
54
|
+
assert result.removed_params == []
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_clean_query_basic() -> None:
|
|
58
|
+
result = clean_query("a=1&utm_source=x&b=2")
|
|
59
|
+
assert result == "a=1&b=2"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_clean_query_all_stripped() -> None:
|
|
63
|
+
result = clean_query("utm_source=x&fbclid=y")
|
|
64
|
+
assert result == ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_clean_query_empty() -> None:
|
|
68
|
+
assert clean_query("") == ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_clean_query_no_trackers() -> None:
|
|
72
|
+
assert clean_query("a=1&b=2") == "a=1&b=2"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_clean_query_custom_patterns() -> None:
|
|
76
|
+
result = clean_query("session=abc&page=1", patterns=["session"])
|
|
77
|
+
assert result == "page=1"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_detrack_result_fields() -> None:
|
|
81
|
+
result = clean("https://example.com/p?utm_source=x")
|
|
82
|
+
assert isinstance(result, DetrackResult)
|
|
83
|
+
assert hasattr(result, "url")
|
|
84
|
+
assert hasattr(result, "parsed_url")
|
|
85
|
+
assert hasattr(result, "cleaned_params")
|
|
86
|
+
assert hasattr(result, "removed_params")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from detrack import clean, clean_query
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_case_insensitivity_utm() -> None:
|
|
5
|
+
for url in [
|
|
6
|
+
"https://example.com?utm_source=x&q=1",
|
|
7
|
+
"https://example.com?UTM_SOURCE=x&q=1",
|
|
8
|
+
"https://example.com?Utm_Source=x&q=1",
|
|
9
|
+
"https://example.com?utm_SOURCE=x&q=1",
|
|
10
|
+
]:
|
|
11
|
+
result = clean(url)
|
|
12
|
+
assert result.url == "https://example.com?q=1", f"Failed for {url}"
|
|
13
|
+
assert result.removed_params == ["utm_source"] or len(result.removed_params) == 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_case_insensitivity_fbclid() -> None:
|
|
17
|
+
for url in [
|
|
18
|
+
"https://example.com?fbclid=x&q=1",
|
|
19
|
+
"https://example.com?FBCLID=x&q=1",
|
|
20
|
+
"https://example.com?Fbclid=x&q=1",
|
|
21
|
+
]:
|
|
22
|
+
result = clean(url)
|
|
23
|
+
assert result.url == "https://example.com?q=1", f"Failed for {url}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_empty_values_preserved_for_non_tracking() -> None:
|
|
27
|
+
result = clean("https://example.com?a=&b=2")
|
|
28
|
+
assert result.url == "https://example.com?a=&b=2"
|
|
29
|
+
assert result.removed_params == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_empty_values_removed_for_tracking() -> None:
|
|
33
|
+
result = clean("https://example.com?utm_source=&b=2")
|
|
34
|
+
assert result.url == "https://example.com?b=2"
|
|
35
|
+
assert result.removed_params == ["utm_source"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_duplicate_params_all_removed() -> None:
|
|
39
|
+
result = clean("https://example.com?utm_source=x&utm_source=y&a=1")
|
|
40
|
+
assert result.url == "https://example.com?a=1"
|
|
41
|
+
assert result.removed_params == ["utm_source", "utm_source"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_url_without_scheme() -> None:
|
|
45
|
+
result = clean("//example.com/page?utm_source=x")
|
|
46
|
+
assert "utm_source" not in result.url
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_url_with_port() -> None:
|
|
50
|
+
result = clean("https://example.com:8080/page?utm_source=x&q=1")
|
|
51
|
+
assert result.url == "https://example.com:8080/page?q=1"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_url_with_auth() -> None:
|
|
55
|
+
result = clean("https://user:pass@example.com/page?utm_source=x&q=1")
|
|
56
|
+
assert result.url == "https://user:pass@example.com/page?q=1"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_only_tracking_params_different_cases() -> None:
|
|
60
|
+
result = clean("https://example.com?UTM_CAMPAIGN=spring&utm_medium=email&q=1")
|
|
61
|
+
assert result.url == "https://example.com?q=1"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_no_changes_with_no_tracking() -> None:
|
|
65
|
+
url = "https://example.com/search?q=python&page=2"
|
|
66
|
+
result = clean(url)
|
|
67
|
+
assert result.url == url
|
|
68
|
+
assert result.cleaned_params == {"q": "python", "page": "2"}
|
|
69
|
+
assert result.removed_params == []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_query_only_function_with_mixed() -> None:
|
|
73
|
+
assert clean_query("a=1&utm_source=x&b=2") == "a=1&b=2"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_query_only_all_tracking() -> None:
|
|
77
|
+
assert clean_query("utm_source=x&fbclid=y") == ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_query_only_empty() -> None:
|
|
81
|
+
assert clean_query("") == ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_query_only_no_tracking() -> None:
|
|
85
|
+
assert clean_query("a=1&b=2") == "a=1&b=2"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from detrack import DEFAULT_PATTERNS
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_default_patterns_count() -> None:
|
|
5
|
+
assert len(DEFAULT_PATTERNS) >= 60, (
|
|
6
|
+
f"DEFAULT_PATTERNS has {len(DEFAULT_PATTERNS)} items, expected >= 60"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_utm_params_present() -> None:
|
|
11
|
+
utm = {"utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content",
|
|
12
|
+
"utm_id", "utm_cid", "utm_reader", "utm_viz", "utm_pubreferrer",
|
|
13
|
+
"utm_nooverride"}
|
|
14
|
+
assert utm.issubset(DEFAULT_PATTERNS), (
|
|
15
|
+
f"Missing UTM params: {utm - DEFAULT_PATTERNS}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_social_params_present() -> None:
|
|
20
|
+
social = {"fbclid", "ref", "source", "si", "sns", "social", "origin"}
|
|
21
|
+
assert social.issubset(DEFAULT_PATTERNS), (
|
|
22
|
+
f"Missing social params: {social - DEFAULT_PATTERNS}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_marketing_params_present() -> None:
|
|
27
|
+
marketing = {"gclid", "gclsrc", "dclid", "msclkid", "wbraid", "gbraid",
|
|
28
|
+
"mc_cid", "mc_eid", "trk", "vero_conv", "vero_id",
|
|
29
|
+
"email", "recipient", "campaign_id", "newsletter", "mbid"}
|
|
30
|
+
assert marketing.issubset(DEFAULT_PATTERNS), (
|
|
31
|
+
f"Missing marketing params: {marketing - DEFAULT_PATTERNS}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_analytics_params_present() -> None:
|
|
36
|
+
analytics = {"_ga", "_gl", "_ke", "_hsenc"}
|
|
37
|
+
assert analytics.issubset(DEFAULT_PATTERNS), (
|
|
38
|
+
f"Missing analytics params: {analytics - DEFAULT_PATTERNS}"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_cache_busters_present() -> None:
|
|
43
|
+
cache = {"_", "cb", "cache", "nocache", "rand", "random", "r", "ts", "_t",
|
|
44
|
+
"timestamp"}
|
|
45
|
+
assert cache.issubset(DEFAULT_PATTERNS), (
|
|
46
|
+
f"Missing cache buster params: {cache - DEFAULT_PATTERNS}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_session_params_present() -> None:
|
|
51
|
+
session = {"session_id", "sid", "phpsessid", "jsessionid", "view_id", "visit_id"}
|
|
52
|
+
assert session.issubset(DEFAULT_PATTERNS), (
|
|
53
|
+
f"Missing session params: {session - DEFAULT_PATTERNS}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_redirect_params_present() -> None:
|
|
58
|
+
redirect = {"redirect_to", "return_to", "next", "continue"}
|
|
59
|
+
assert redirect.issubset(DEFAULT_PATTERNS), (
|
|
60
|
+
f"Missing redirect params: {redirect - DEFAULT_PATTERNS}"
|
|
61
|
+
)
|