affiliate_tracker 0.3.1
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +37 -0
- data/.ruby-version +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +240 -0
- data/Rakefile +12 -0
- data/TEST_SUITE_TASKS.md +238 -0
- data/app/controllers/affiliate_tracker/application_controller.rb +7 -0
- data/app/controllers/affiliate_tracker/clicks_controller.rb +110 -0
- data/app/controllers/affiliate_tracker/dashboard_controller.rb +30 -0
- data/app/models/affiliate_tracker/click.rb +22 -0
- data/app/views/affiliate_tracker/dashboard/index.html.erb +91 -0
- data/config/routes.rb +6 -0
- data/lib/affiliate_tracker/configuration.rb +100 -0
- data/lib/affiliate_tracker/engine.rb +19 -0
- data/lib/affiliate_tracker/url_generator.rb +72 -0
- data/lib/affiliate_tracker/version.rb +5 -0
- data/lib/affiliate_tracker/view_helpers.rb +67 -0
- data/lib/affiliate_tracker.rb +34 -0
- data/lib/generators/affiliate_tracker/install/install_generator.rb +40 -0
- data/lib/generators/affiliate_tracker/install/templates/README +19 -0
- data/lib/generators/affiliate_tracker/install/templates/create_affiliate_tracker_clicks.rb.tt +20 -0
- data/lib/generators/affiliate_tracker/install/templates/initializer.rb +26 -0
- metadata +131 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 825b530c03d5358a02b7c718dea8a983a28b6f7d410708baaadedcd8dd085983
|
|
4
|
+
data.tar.gz: 540f309d350fd3326c5668fda1101a8212871b1283e3b2858dfd41d790c11658
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 37821ddde6e55a7522546626e1e2a5527dde7e929048573401c46d4b3e94e97ae2e92ea911e663a12da7ed200a0c9bd638642d098cdaf226ee00059b664690dc
|
|
7
|
+
data.tar.gz: 335f997f6b7da97d732e2d27bd7915ed78bd35b4546b66418f60789b3cea2c44d2c0bf0d11c40dbdb56066cfb996889062f5398317d45c2c3f3fdc101d51d796
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.2
|
|
3
|
+
NewCops: enable
|
|
4
|
+
|
|
5
|
+
Style/StringLiterals:
|
|
6
|
+
EnforcedStyle: double_quotes
|
|
7
|
+
|
|
8
|
+
Style/StringLiteralsInInterpolation:
|
|
9
|
+
EnforcedStyle: double_quotes
|
|
10
|
+
|
|
11
|
+
Layout/LineLength:
|
|
12
|
+
Max: 140
|
|
13
|
+
|
|
14
|
+
Metrics/BlockLength:
|
|
15
|
+
Exclude:
|
|
16
|
+
- "test/**/*"
|
|
17
|
+
|
|
18
|
+
Metrics/ClassLength:
|
|
19
|
+
Max: 150
|
|
20
|
+
|
|
21
|
+
Metrics/MethodLength:
|
|
22
|
+
Max: 30
|
|
23
|
+
|
|
24
|
+
Metrics/ModuleLength:
|
|
25
|
+
Max: 150
|
|
26
|
+
|
|
27
|
+
Metrics/AbcSize:
|
|
28
|
+
Max: 35
|
|
29
|
+
|
|
30
|
+
Metrics/CyclomaticComplexity:
|
|
31
|
+
Max: 15
|
|
32
|
+
|
|
33
|
+
Metrics/PerceivedComplexity:
|
|
34
|
+
Max: 15
|
|
35
|
+
|
|
36
|
+
Style/Documentation:
|
|
37
|
+
Enabled: false
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.4
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.1] - 2026-03-17
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Redirect status changed from 301 (`:moved_permanently`) to 302 (`:found`). 301s are cached permanently by browsers, which prevents click re-tracking on subsequent visits.
|
|
7
|
+
- IPv6 anonymization: `anonymize_ip` now handles IPv6 addresses by zeroing the last 80 bits (last 5 groups), in addition to the existing IPv4 last-octet zeroing.
|
|
8
|
+
- CI matrix expanded to test Ruby 3.2, 3.3, and 3.4.
|
|
9
|
+
- Gem prepared for RubyGems.org publication: added LICENSE.txt, CHANGELOG.md to gem files, MFA requirement, upper-bound Rails dependency (`< 10`).
|
|
10
|
+
|
|
11
|
+
## [0.3.0] - 2026-03-17
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- URL normalization: URLs without a protocol (e.g. `shop.com/page`) are automatically prepended with `https://` both at URL generation time and at redirect time. Prevents broken redirects for malformed destination URLs.
|
|
15
|
+
- Tests for URL normalization in `UrlGenerator` and `ClicksController`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `UrlGenerator#initialize` now normalizes destination URLs before encoding.
|
|
19
|
+
- `ClicksController#redirect` normalizes destination URLs before appending UTM params and redirecting.
|
|
20
|
+
|
|
21
|
+
## [0.2.0] - 2026-01-24
|
|
22
|
+
|
|
23
|
+
- Per-link metadata override for UTM params (`utm_source`, `utm_medium`).
|
|
24
|
+
- Configurable `after_click` callback.
|
|
25
|
+
- Proc-based `fallback_url` with untrusted payload data.
|
|
26
|
+
- Click deduplication (same IP + URL within 5s).
|
|
27
|
+
|
|
28
|
+
## [0.1.0] - 2025-12-15
|
|
29
|
+
|
|
30
|
+
- Initial release with click tracking, UTM injection, and redirect.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Justyna Wojtczak
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# AffiliateTracker
|
|
2
|
+
|
|
3
|
+
Click tracking for affiliates working with small e-commerce shops. Track your clicks, add UTM params, prove your value.
|
|
4
|
+
|
|
5
|
+
## Who is this for?
|
|
6
|
+
|
|
7
|
+
**You're an affiliate/influencer** who promotes products from small shops (Shoplo, Shoper, WooCommerce, IdoSell, etc.) that **don't have their own affiliate system**.
|
|
8
|
+
|
|
9
|
+
You send traffic via newsletters, blogs, or social media — but you have no way to prove how many clicks you actually sent. The shop owner sees some visits in Google Analytics, but can't tell which came from you.
|
|
10
|
+
|
|
11
|
+
**This gem solves that:**
|
|
12
|
+
- You track every click on your side (proof for negotiations)
|
|
13
|
+
- Links automatically include UTM parameters (shop sees your traffic in their GA)
|
|
14
|
+
- Optional `ref=` parameter for shops that support simple referral tracking
|
|
15
|
+
|
|
16
|
+
**Not for:** Amazon, eBay, or platforms with existing affiliate programs (they have their own tracking and often prohibit link masking).
|
|
17
|
+
|
|
18
|
+
## Problem
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
You: "I sent you 500 clicks this month"
|
|
22
|
+
Shop: "Google Analytics shows only 200 visits"
|
|
23
|
+
You: "..."
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Solution
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Your email → User clicks → AffiliateTracker counts → Redirect with UTM → Shop sees source
|
|
30
|
+
↓
|
|
31
|
+
You have proof: 500 clicks
|
|
32
|
+
Shop sees: utm_source=yourname
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Click tracking with metadata (shop, campaign, etc.)
|
|
38
|
+
- Automatic UTM parameter injection
|
|
39
|
+
- Click deduplication (same IP + URL within 5s counted once)
|
|
40
|
+
- Built-in dashboard
|
|
41
|
+
- Rails 8+ / zero configuration
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
gem "affiliate_tracker"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or, for the latest development version:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
gem "affiliate_tracker", git: "https://github.com/justi-blue/affiliate_tracker"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
rails generate affiliate_tracker:install
|
|
57
|
+
rails db:migrate
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### affiliate_link helper
|
|
63
|
+
|
|
64
|
+
```erb
|
|
65
|
+
<%# Simple link %>
|
|
66
|
+
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz sukienkę" %>
|
|
67
|
+
|
|
68
|
+
<%# With metadata %>
|
|
69
|
+
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz sukienkę",
|
|
70
|
+
shop: "modago",
|
|
71
|
+
campaign: "homepage" %>
|
|
72
|
+
|
|
73
|
+
<%# With CSS classes %>
|
|
74
|
+
<%= affiliate_link "https://modago.pl/sukienka", "Zobacz",
|
|
75
|
+
shop: "modago",
|
|
76
|
+
class: "btn btn-primary" %>
|
|
77
|
+
|
|
78
|
+
<%# Block syntax %>
|
|
79
|
+
<%= affiliate_link "https://modago.pl/sukienka", shop: "modago" do %>
|
|
80
|
+
<img src="photo.jpg"> Zobacz ofertę
|
|
81
|
+
<% end %>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Generates:**
|
|
85
|
+
```html
|
|
86
|
+
<a href="https://yourapp.com/a/eyJ...?s=abc" target="_blank" rel="noopener">
|
|
87
|
+
Zobacz sukienkę
|
|
88
|
+
</a>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### affiliate_url helper (URL only)
|
|
92
|
+
|
|
93
|
+
```erb
|
|
94
|
+
<a href="<%= affiliate_url 'https://modago.pl/sukienka', shop: 'modago' %>">
|
|
95
|
+
Custom link
|
|
96
|
+
</a>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### User Tracking
|
|
100
|
+
|
|
101
|
+
Track which user clicked a link by passing `user_id`:
|
|
102
|
+
|
|
103
|
+
```erb
|
|
104
|
+
<%# On web pages - use Current.user %>
|
|
105
|
+
<%= affiliate_link "https://shop.com/product", "Buy Now",
|
|
106
|
+
user_id: Current.user&.id,
|
|
107
|
+
campaign: "homepage" %>
|
|
108
|
+
|
|
109
|
+
<%# In mailers - pass user explicitly (Current.user not available in background jobs) %>
|
|
110
|
+
<%= affiliate_link "https://shop.com/product", "View Deal",
|
|
111
|
+
user_id: @user.id,
|
|
112
|
+
shop_id: @shop.id,
|
|
113
|
+
campaign: "daily_digest" %>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Common tracking parameters:
|
|
117
|
+
- `user_id` - User who clicked (for attribution)
|
|
118
|
+
- `shop_id` - Shop identifier
|
|
119
|
+
- `promotion_id` - Specific promotion
|
|
120
|
+
- `campaign` - Campaign name (e.g., "daily_digest", "homepage")
|
|
121
|
+
|
|
122
|
+
### In Mailers
|
|
123
|
+
|
|
124
|
+
```erb
|
|
125
|
+
<%# app/views/digest_mailer/weekly.html.erb %>
|
|
126
|
+
<% @promotions.each do |promo| %>
|
|
127
|
+
<%= affiliate_link promo.shop.website_url, "Zobacz promocję",
|
|
128
|
+
user_id: @user.id,
|
|
129
|
+
shop_id: promo.shop.id,
|
|
130
|
+
promotion_id: promo.id,
|
|
131
|
+
campaign: "weekly_digest" %>
|
|
132
|
+
<% end %>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Real Example: Shoplo Store
|
|
136
|
+
|
|
137
|
+
```erb
|
|
138
|
+
<%# Just the product URL - ref param added automatically from config %>
|
|
139
|
+
<%= affiliate_link "https://demo.shoplo.com/koszulka-bawelniana",
|
|
140
|
+
"Zobacz koszulkę",
|
|
141
|
+
shop: "shoplo-demo",
|
|
142
|
+
campaign: "styczen2025" %>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**User clicks → AffiliateTracker counts → Redirects to:**
|
|
146
|
+
```
|
|
147
|
+
https://demo.shoplo.com/koszulka-bawelniana?ref=partnerJan&utm_source=smartoffers&utm_medium=email&utm_campaign=styczen2025&utm_content=shoplo-demo
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The shop sees:
|
|
151
|
+
- `ref=partnerJan` - from `config.ref_param` (automatic)
|
|
152
|
+
- UTM params - in Google Analytics
|
|
153
|
+
|
|
154
|
+
### Result
|
|
155
|
+
|
|
156
|
+
1. Generates: `https://yourapp.com/a/eyJ...?s=abc`
|
|
157
|
+
2. On click, redirects to: `https://modago.pl/sukienka?utm_source=smartoffers&utm_medium=email&utm_campaign=weekly_digest&utm_content=modago`
|
|
158
|
+
|
|
159
|
+
### Configuration
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
# config/initializers/affiliate_tracker.rb
|
|
163
|
+
AffiliateTracker.configure do |config|
|
|
164
|
+
# Your brand name (appears in utm_source)
|
|
165
|
+
config.utm_source = "smartoffers"
|
|
166
|
+
|
|
167
|
+
# Default medium
|
|
168
|
+
config.utm_medium = "email"
|
|
169
|
+
|
|
170
|
+
# Referral param (adds ?ref=partnerJan to all links)
|
|
171
|
+
config.ref_param = "partnerJan"
|
|
172
|
+
|
|
173
|
+
# Dashboard auth
|
|
174
|
+
config.authenticate_dashboard = -> {
|
|
175
|
+
redirect_to main_app.login_path unless current_user&.admin?
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Tracking Parameters
|
|
181
|
+
|
|
182
|
+
| Parameter | Source | Example |
|
|
183
|
+
|-----------|--------|---------|
|
|
184
|
+
| `ref` | `config.ref_param` | `partnerJan` |
|
|
185
|
+
| `utm_source` | `config.utm_source` | `smartoffers` |
|
|
186
|
+
| `utm_medium` | `config.utm_medium` | `email` |
|
|
187
|
+
| `utm_campaign` | `campaign:` in helper | `weekly_digest` |
|
|
188
|
+
| `utm_content` | `shop:` in helper | `modago` |
|
|
189
|
+
|
|
190
|
+
Override defaults per-link:
|
|
191
|
+
|
|
192
|
+
```erb
|
|
193
|
+
<%= affiliate_url "https://shop.com",
|
|
194
|
+
utm_source: "newsletter",
|
|
195
|
+
utm_medium: "email",
|
|
196
|
+
campaign: "black_friday" %>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Dashboard
|
|
200
|
+
|
|
201
|
+
Access at `/a/dashboard`
|
|
202
|
+
|
|
203
|
+
Shows:
|
|
204
|
+
- Total clicks
|
|
205
|
+
- Clicks today/this week
|
|
206
|
+
- Top destinations (shops)
|
|
207
|
+
- Recent clicks with metadata
|
|
208
|
+
|
|
209
|
+
## Security
|
|
210
|
+
|
|
211
|
+
- Links are signed with **HMAC-SHA256** (128-bit truncated signature per RFC 2104)
|
|
212
|
+
- Signature verification uses constant-time comparison (`ActiveSupport::SecurityUtils`)
|
|
213
|
+
- Invalid or missing signatures result in a **302 redirect** to a configurable fallback URL (default: `/`)
|
|
214
|
+
- Payload is Base64-encoded JSON (not encrypted) — metadata is readable but tamper-proof
|
|
215
|
+
|
|
216
|
+
### Fallback URL
|
|
217
|
+
|
|
218
|
+
When a user visits a link with an invalid or missing signature (e.g., bots stripping query params), the gem redirects instead of returning an error:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
AffiliateTracker.configure do |config|
|
|
222
|
+
# Static URL (default)
|
|
223
|
+
config.fallback_url = "/"
|
|
224
|
+
|
|
225
|
+
# Dynamic — receives decoded payload Hash (unverified, treat as untrusted)
|
|
226
|
+
config.fallback_url = ->(payload) {
|
|
227
|
+
slug = payload&.dig("shop")
|
|
228
|
+
slug.present? ? "/shops/#{slug}" : "/"
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## For Shop Owners
|
|
234
|
+
|
|
235
|
+
Tell your partner shops:
|
|
236
|
+
> "All my links include UTM parameters. Check Google Analytics → Acquisition → Traffic Sources → filter by `utm_source=yourname`"
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
data/Rakefile
ADDED
data/TEST_SUITE_TASKS.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Test Suite Tasks
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Bring the gem's test suite closer to real user flows, cover missing edge cases, and reduce low-value duplication.
|
|
6
|
+
|
|
7
|
+
## Priority 1: Cover the real runtime flow
|
|
8
|
+
|
|
9
|
+
### Task 1: Add integration tests for click redirect happy path
|
|
10
|
+
|
|
11
|
+
- Add request or integration tests for `AffiliateTracker::ClicksController#redirect`.
|
|
12
|
+
- Verify valid signed URLs:
|
|
13
|
+
- decode correctly,
|
|
14
|
+
- create a click record,
|
|
15
|
+
- redirect to the destination URL,
|
|
16
|
+
- append expected UTM params,
|
|
17
|
+
- preserve existing query params.
|
|
18
|
+
- Verify response status and redirect target exactly.
|
|
19
|
+
|
|
20
|
+
**Why**
|
|
21
|
+
|
|
22
|
+
Current tests mostly cover URL generation, but not the main production flow after a user clicks a link.
|
|
23
|
+
|
|
24
|
+
**Suggested assertions**
|
|
25
|
+
|
|
26
|
+
- click count increases by 1
|
|
27
|
+
- saved `destination_url` matches decoded payload
|
|
28
|
+
- redirect URL contains `utm_source`, `utm_medium`, optional `utm_campaign`, optional `utm_content`
|
|
29
|
+
- existing query params are preserved
|
|
30
|
+
|
|
31
|
+
### Task 2: Add integration tests for invalid or missing signature
|
|
32
|
+
|
|
33
|
+
- Test invalid signature redirects to fallback URL.
|
|
34
|
+
- Test missing signature redirects to fallback URL.
|
|
35
|
+
- Test tampered payload redirects to fallback URL.
|
|
36
|
+
- Test fallback works for:
|
|
37
|
+
- static string config,
|
|
38
|
+
- proc-based config with decoded untrusted payload,
|
|
39
|
+
- corrupt payload passed into proc fallback.
|
|
40
|
+
|
|
41
|
+
**Why**
|
|
42
|
+
|
|
43
|
+
This is a key resilience path documented in the gem, but it is not covered end-to-end.
|
|
44
|
+
|
|
45
|
+
### Task 3: Add integration tests for click deduplication
|
|
46
|
+
|
|
47
|
+
- Exercise two requests through the controller, not directly through `Rails.cache`.
|
|
48
|
+
- Verify same IP + same destination within 5 seconds creates only one click.
|
|
49
|
+
- Verify different IP still records a second click.
|
|
50
|
+
- Verify different destination still records a second click.
|
|
51
|
+
- Verify click is recorded again after dedup window expires or is cleared.
|
|
52
|
+
|
|
53
|
+
**Why**
|
|
54
|
+
|
|
55
|
+
Current deduplication tests check cache primitives, not actual dedup behavior in production code.
|
|
56
|
+
|
|
57
|
+
### Task 4: Add integration tests for `after_click`
|
|
58
|
+
|
|
59
|
+
- Verify configured `after_click` handler is called after a successful click record.
|
|
60
|
+
- Verify handler receives the created `AffiliateTracker::Click`.
|
|
61
|
+
- Verify exceptions in handler do not break redirect flow.
|
|
62
|
+
|
|
63
|
+
**Why**
|
|
64
|
+
|
|
65
|
+
`after_click` is part of the public configuration API and should be regression-safe.
|
|
66
|
+
|
|
67
|
+
### Task 5: Add integration tests for request metadata handling
|
|
68
|
+
|
|
69
|
+
- Verify IP is anonymized before persisting.
|
|
70
|
+
- Verify `user_agent` is truncated to 500 chars.
|
|
71
|
+
- Verify `referer` is truncated to 500 chars.
|
|
72
|
+
- Verify metadata is stored as expected.
|
|
73
|
+
|
|
74
|
+
**Why**
|
|
75
|
+
|
|
76
|
+
This logic exists in the controller and affects privacy and database safety, but is currently untested.
|
|
77
|
+
|
|
78
|
+
## Priority 2: Cover dashboard and model behavior
|
|
79
|
+
|
|
80
|
+
### Task 6: Add controller/integration tests for dashboard access
|
|
81
|
+
|
|
82
|
+
- Verify `/a/dashboard` resolves correctly.
|
|
83
|
+
- Verify dashboard renders when no auth proc is configured.
|
|
84
|
+
- Verify configured `authenticate_dashboard` proc is executed.
|
|
85
|
+
- Verify auth proc can redirect unauthenticated users.
|
|
86
|
+
|
|
87
|
+
**Why**
|
|
88
|
+
|
|
89
|
+
Dashboard access is a user-facing feature and currently has no behavioral coverage.
|
|
90
|
+
|
|
91
|
+
### Task 7: Add dashboard stats tests
|
|
92
|
+
|
|
93
|
+
- Seed clicks across different timestamps.
|
|
94
|
+
- Verify:
|
|
95
|
+
- `total_clicks`
|
|
96
|
+
- `today_clicks`
|
|
97
|
+
- `week_clicks`
|
|
98
|
+
- `unique_destinations`
|
|
99
|
+
- recent clicks ordering
|
|
100
|
+
- top destinations counts
|
|
101
|
+
|
|
102
|
+
**Why**
|
|
103
|
+
|
|
104
|
+
The dashboard is mostly aggregation logic. That kind of logic regresses easily when queries change.
|
|
105
|
+
|
|
106
|
+
### Task 8: Add model tests for `AffiliateTracker::Click`
|
|
107
|
+
|
|
108
|
+
- Test validations for `destination_url` and `clicked_at`.
|
|
109
|
+
- Test `domain` with:
|
|
110
|
+
- valid URL,
|
|
111
|
+
- invalid URL,
|
|
112
|
+
- URL without host if relevant.
|
|
113
|
+
- Test scopes:
|
|
114
|
+
- `.today`
|
|
115
|
+
- `.this_week`
|
|
116
|
+
- `.this_month`
|
|
117
|
+
|
|
118
|
+
**Why**
|
|
119
|
+
|
|
120
|
+
The model has public behavior that is not currently covered at all.
|
|
121
|
+
|
|
122
|
+
## Priority 3: Fix test architecture issues
|
|
123
|
+
|
|
124
|
+
### Task 9: Stop stubbing the gem's public API in `test/test_helper.rb`
|
|
125
|
+
|
|
126
|
+
- Load the real `lib/affiliate_tracker.rb` where possible.
|
|
127
|
+
- Remove duplicated test-only implementations of:
|
|
128
|
+
- `AffiliateTracker.configure`
|
|
129
|
+
- `AffiliateTracker.track_url`
|
|
130
|
+
- `AffiliateTracker.url`
|
|
131
|
+
- Keep only the minimal Rails test scaffolding needed by the gem.
|
|
132
|
+
|
|
133
|
+
**Why**
|
|
134
|
+
|
|
135
|
+
Current tests can pass even if the real entry point breaks, especially around `default_metadata`.
|
|
136
|
+
|
|
137
|
+
### Task 10: Add tests for `default_metadata`
|
|
138
|
+
|
|
139
|
+
- Verify configured `default_metadata` is merged into generated tracking URLs.
|
|
140
|
+
- Verify explicit per-link metadata overrides default values where expected.
|
|
141
|
+
- Verify non-hash return value falls back to `{}`.
|
|
142
|
+
- Verify exceptions inside `default_metadata` do not break URL generation.
|
|
143
|
+
|
|
144
|
+
**Why**
|
|
145
|
+
|
|
146
|
+
This is real logic in the gem entry point and is currently untested because the suite bypasses it.
|
|
147
|
+
|
|
148
|
+
## Priority 4: Replace low-value indirect tests
|
|
149
|
+
|
|
150
|
+
### Task 11: Replace route file parsing tests with routing behavior tests
|
|
151
|
+
|
|
152
|
+
- Replace string-based assertions against `config/routes.rb`.
|
|
153
|
+
- Add routing tests that verify:
|
|
154
|
+
- `/a/dashboard` routes to dashboard controller,
|
|
155
|
+
- `/a/:payload` routes to click redirect,
|
|
156
|
+
- dashboard route is not swallowed by payload route.
|
|
157
|
+
|
|
158
|
+
**Why**
|
|
159
|
+
|
|
160
|
+
Parsing the routes file as text is brittle and does not prove the router behaves correctly.
|
|
161
|
+
|
|
162
|
+
### Task 12: Remove or rewrite the Base64 `dashboard` test
|
|
163
|
+
|
|
164
|
+
- Delete or replace the test asserting `"dashboard"` is not valid Base64 payload.
|
|
165
|
+
- If kept, justify it with actual runtime behavior.
|
|
166
|
+
|
|
167
|
+
**Why**
|
|
168
|
+
|
|
169
|
+
That test does not meaningfully protect routing behavior.
|
|
170
|
+
|
|
171
|
+
## Priority 5: Reduce redundant coverage
|
|
172
|
+
|
|
173
|
+
### Task 13: Consolidate overlapping URL generation tests
|
|
174
|
+
|
|
175
|
+
- Review overlap between:
|
|
176
|
+
- `ConfigurationTest`
|
|
177
|
+
- `UrlGeneratorTest`
|
|
178
|
+
- `ViewHelpersTest`
|
|
179
|
+
- Keep one focused place for:
|
|
180
|
+
- signature format,
|
|
181
|
+
- `/a/` URL structure,
|
|
182
|
+
- same input => same output,
|
|
183
|
+
- different input => different signature.
|
|
184
|
+
|
|
185
|
+
**Why**
|
|
186
|
+
|
|
187
|
+
The suite repeats the same contract several times with little extra protection.
|
|
188
|
+
|
|
189
|
+
### Task 14: Trim repetitive helper tests
|
|
190
|
+
|
|
191
|
+
- Keep tests that cover real helper-specific behavior:
|
|
192
|
+
- HTML escaping,
|
|
193
|
+
- block syntax,
|
|
194
|
+
- HTML attributes vs tracking metadata split,
|
|
195
|
+
- default `target` and `rel`,
|
|
196
|
+
- overriding `target` and `rel`.
|
|
197
|
+
- Reduce repeated cases that only re-prove `UrlGenerator.decode`.
|
|
198
|
+
|
|
199
|
+
**Why**
|
|
200
|
+
|
|
201
|
+
`ViewHelpersTest` is large, but much of it duplicates lower-level URL generator coverage.
|
|
202
|
+
|
|
203
|
+
## Optional: Installer and engine coverage
|
|
204
|
+
|
|
205
|
+
### Task 15: Add tests for engine helper inclusion
|
|
206
|
+
|
|
207
|
+
- Verify helpers are available in Action View.
|
|
208
|
+
- Verify helpers are available in Action Mailer if the gem promises that behavior.
|
|
209
|
+
|
|
210
|
+
### Task 16: Add generator tests
|
|
211
|
+
|
|
212
|
+
- Verify install generator:
|
|
213
|
+
- creates initializer,
|
|
214
|
+
- creates migration,
|
|
215
|
+
- mounts engine route.
|
|
216
|
+
|
|
217
|
+
**Why**
|
|
218
|
+
|
|
219
|
+
For a gem, installation and framework integration are part of the product surface.
|
|
220
|
+
|
|
221
|
+
## Suggested execution order
|
|
222
|
+
|
|
223
|
+
1. Add redirect integration tests.
|
|
224
|
+
2. Add deduplication and fallback tests.
|
|
225
|
+
3. Add dashboard and model tests.
|
|
226
|
+
4. Fix `test_helper` to use the real gem entry point.
|
|
227
|
+
5. Replace indirect route tests.
|
|
228
|
+
6. Remove redundant helper and URL tests.
|
|
229
|
+
|
|
230
|
+
## Definition of done
|
|
231
|
+
|
|
232
|
+
- Main click flow is covered end-to-end.
|
|
233
|
+
- Failure paths are covered end-to-end.
|
|
234
|
+
- Public config hooks are covered.
|
|
235
|
+
- Dashboard behavior is covered.
|
|
236
|
+
- Model behavior is covered.
|
|
237
|
+
- Tests execute against real production entry points, not test-only rewrites.
|
|
238
|
+
- Redundant tests are reduced without losing regression protection.
|