Skip to content

Commit 5236c59

Browse files
authored
Log client_id in addition to visit_id (#156)
1 parent fe3c9c7 commit 5236c59

File tree

13 files changed

+1709
-75
lines changed

13 files changed

+1709
-75
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/pkg/errors v0.9.1
1313
github.com/sirupsen/logrus v1.9.0
1414
go.mozilla.org/mozlogrus v1.0.0
15+
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
1516
)
1617

1718
require (

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
8787
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
8888
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
8989
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
90+
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
91+
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
9092
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
9193
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
9294
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=

stubservice/stubhandlers/stubhandler_test.go

+159-65
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,21 @@ import (
1414

1515
"github.com/mozilla-services/stubattribution/attributioncode"
1616
"github.com/mozilla-services/stubattribution/stubservice/backends"
17+
"github.com/sirupsen/logrus"
18+
"github.com/sirupsen/logrus/hooks/test"
19+
"golang.org/x/exp/slices"
1720
)
1821

22+
// testHook is a logrus hook that is registered globally in order to capture
23+
// the messages logged during the execution of the tests. This is useful to
24+
// verify some important log statements. We have to register a global hook
25+
// because we use the global logrus instance in our code.
26+
var testHook *test.Hook
27+
28+
func init() {
29+
testHook = test.NewGlobal()
30+
}
31+
1932
func TestUniqueKey(t *testing.T) {
2033
f := func(url, code string) bool {
2134
key := uniqueKey(url, code)
@@ -137,15 +150,95 @@ func TestRedirectFull(t *testing.T) {
137150

138151
svc := NewStubService(
139152
NewRedirectHandler(storage, server.URL+"/cdn/", ""),
140-
&attributioncode.Validator{})
153+
&attributioncode.Validator{},
154+
)
155+
156+
for _, params := range []struct {
157+
AttributionCode string
158+
Referer string
159+
ExpectedLocation string
160+
ExpectedCode string
161+
SkipDownloadLogChecks bool
162+
ExpectedClientID string
163+
ExpectedSessionID string
164+
}{
165+
{
166+
AttributionCode: `campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.google.com`,
167+
Referer: "",
168+
ExpectedLocation: `/cdn/builds/firefox-stub/en-US/win/`,
169+
ExpectedCode: `campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3Dwww.google.com`,
170+
},
171+
{
172+
AttributionCode: `campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.notinwhitelist.com`,
173+
Referer: "",
174+
ExpectedLocation: `/cdn/builds/firefox-stub/en-US/win/`,
175+
ExpectedCode: `campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3D%2528other%2529`,
176+
},
177+
{
178+
// We expect the product to be prefixed in the location URL below because
179+
// the attribution code contains data for RTAMO and the referer header
180+
// contains the right value.
181+
AttributionCode: `campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org`,
182+
Referer: `https://www.mozilla.org/`,
183+
ExpectedLocation: `/cdn/builds/rtamo-firefox-stub/en-US/win/`,
184+
ExpectedCode: `campaign%3Dfxa-cta-123%26content%3Drta%253Avalue%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
185+
},
186+
{
187+
// Same as before with a `client_id`.
188+
AttributionCode: `campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org&client_id=some-client-id`,
189+
Referer: `https://www.mozilla.org/`,
190+
ExpectedLocation: `/cdn/builds/rtamo-firefox-stub/en-US/win/`,
191+
ExpectedCode: `campaign%3Dfxa-cta-123%26content%3Drta%253Avalue%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
192+
ExpectedClientID: "some-client-id",
193+
},
194+
{
195+
// Same as before with a `visit_id`.
196+
AttributionCode: `campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org&visit_id=some-visit-id`,
197+
Referer: `https://www.mozilla.org/`,
198+
ExpectedLocation: `/cdn/builds/rtamo-firefox-stub/en-US/win/`,
199+
ExpectedCode: `campaign%3Dfxa-cta-123%26content%3Drta%253Avalue%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
200+
ExpectedClientID: "some-visit-id",
201+
},
202+
{
203+
// Same as before with the addition of a `session_id`.
204+
AttributionCode: `campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org&visit_id=some-visit-id&session_id=some-session-id`,
205+
Referer: `https://www.mozilla.org/`,
206+
ExpectedLocation: `/cdn/builds/rtamo-firefox-stub/en-US/win/`,
207+
ExpectedCode: `campaign%3Dfxa-cta-123%26content%3Drta%253Avalue%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
208+
ExpectedClientID: "some-visit-id",
209+
ExpectedSessionID: "some-session-id",
210+
},
211+
{
212+
// We expect no prefix because the attribution data is not related to
213+
// RTAMO.
214+
AttributionCode: `campaign=some-campaign&content=not-for-rtamo&medium=referral&source=addons.mozilla.org`,
215+
Referer: `https://www.mozilla.org/`,
216+
ExpectedLocation: `/cdn/builds/firefox-stub/en-US/win/`,
217+
ExpectedCode: `campaign%3Dsome-campaign%26content%3Dnot-for-rtamo%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
218+
},
219+
{
220+
// This should not return a modified installer because the referer is not
221+
// the expected one.
222+
AttributionCode: `campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org`,
223+
Referer: `https://example.org/`,
224+
ExpectedLocation: `\?lang=en-US&os=win&product=firefox-stub`,
225+
ExpectedCode: "",
226+
SkipDownloadLogChecks: true,
227+
},
228+
} {
229+
testHook.Reset()
230+
231+
expectedLocationRegexp := regexp.MustCompile(params.ExpectedLocation)
232+
expectedCodeRegexp := regexp.MustCompile(params.ExpectedCode)
141233

142-
runTest := func(attributionCode, referer string, expectedLocation string, expectedCode string) {
143-
expectedCodeRegexp := regexp.MustCompile(expectedCode)
144-
expectedLocationRegexp := regexp.MustCompile(expectedLocation)
145234
recorder := httptest.NewRecorder()
146-
base64Code := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(attributionCode))
147-
req := httptest.NewRequest("GET", `http://test/?product=firefox-stub&os=win&lang=en-US&attribution_code=`+url.QueryEscape(base64Code), nil)
148-
req.Header.Set("Referer", referer)
235+
base64Code := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(params.AttributionCode))
236+
req := httptest.NewRequest(
237+
"GET",
238+
`http://test/?product=firefox-stub&os=win&lang=en-US&attribution_code=`+url.QueryEscape(base64Code),
239+
nil,
240+
)
241+
req.Header.Set("Referer", params.Referer)
149242
svc.ServeHTTP(recorder, req)
150243

151244
location := recorder.HeaderMap.Get("Location")
@@ -169,53 +262,48 @@ func TestRedirectFull(t *testing.T) {
169262
if err != nil {
170263
t.Fatal("could not read body", err)
171264
}
172-
173265
if len(bodyBytes) != len(testFileBytes) {
174266
t.Error("Returned file was not the same length as the original file")
175267
}
176-
177268
if !expectedCodeRegexp.Match(bodyBytes) {
178269
t.Error("Returned file did not contain attribution code")
179270
}
180-
}
181271

182-
emptyReferer := ""
183-
runTest(
184-
`campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.google.com`,
185-
emptyReferer,
186-
`/cdn/builds/firefox-stub/en-US/win/`,
187-
`campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3Dwww.google.com`,
188-
)
189-
runTest(
190-
`campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.notinwhitelist.com`,
191-
emptyReferer,
192-
`/cdn/builds/firefox-stub/en-US/win/`,
193-
`campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3D%2528other%2529`,
194-
)
195-
// We expect the product to be prefixed in the location URL below because the
196-
// attribution code contains data for RTAMO and the referer header contains
197-
// the right value.
198-
runTest(
199-
`campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org`,
200-
`https://www.mozilla.org/`,
201-
`/cdn/builds/rtamo-firefox-stub/en-US/win/`,
202-
`campaign%3Dfxa-cta-123%26content%3Drta%253Avalue%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
203-
)
204-
// We expect no prefix because the attribution data is not related to RTAMO.
205-
runTest(
206-
`campaign=some-campaign&content=not-for-rtamo&medium=referral&source=addons.mozilla.org`,
207-
`https://www.mozilla.org/`,
208-
`/cdn/builds/firefox-stub/en-US/win/`,
209-
`campaign%3Dsome-campaign%26content%3Dnot-for-rtamo%26dltoken%3D[\w\d-]+%26medium%3Dreferral%26source%3Daddons.mozilla.org`,
210-
)
211-
// This should not return a modified installer because the referer is not the
212-
// expected one.
213-
runTest(
214-
`campaign=fxa-cta-123&content=rta:value&medium=referral&source=addons.mozilla.org`,
215-
`https://example.org/`,
216-
`\?lang=en-US&os=win&product=firefox-stub`,
217-
``,
218-
)
272+
if !params.SkipDownloadLogChecks {
273+
entries := testHook.AllEntries()
274+
275+
idx := slices.IndexFunc(entries, func(e *logrus.Entry) bool {
276+
return e.Message == "Download Started"
277+
})
278+
if idx == -1 {
279+
t.Error("Could not find Download Started log entry")
280+
}
281+
downloadStarted := entries[idx]
282+
283+
// This one should always be the last log entry.
284+
downloadFinished := entries[len(entries)-1]
285+
if downloadFinished.Message != "Download Finished" {
286+
t.Errorf("Unexpected log message: %s", downloadFinished.Message)
287+
}
288+
289+
for _, entry := range []*logrus.Entry{downloadStarted, downloadFinished} {
290+
clientID := entry.Data["client_id"]
291+
if clientID != params.ExpectedClientID {
292+
t.Errorf("Expected client_id: %s, got: %v", params.ExpectedClientID, clientID)
293+
}
294+
295+
visitID := entry.Data["visit_id"]
296+
if visitID != params.ExpectedClientID {
297+
t.Errorf("Expected visit_id: %s, got: %v", params.ExpectedClientID, visitID)
298+
}
299+
300+
sessionID := entry.Data["session_id"]
301+
if sessionID != params.ExpectedSessionID {
302+
t.Errorf("Expected session_id: %s, got: %v", params.ExpectedSessionID, sessionID)
303+
}
304+
}
305+
}
306+
}
219307
}
220308

221309
func TestDirectFull(t *testing.T) {
@@ -245,14 +333,33 @@ func TestDirectFull(t *testing.T) {
245333

246334
svc := NewStubService(
247335
NewDirectHandler(),
248-
&attributioncode.Validator{})
336+
&attributioncode.Validator{},
337+
)
249338

250-
runTest := func(attributionCode, expectedCode string) {
251-
expectedCodeRegexp := regexp.MustCompile(expectedCode)
252-
base64Code := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(attributionCode))
253-
req := httptest.NewRequest("GET", `http://test/?product=firefox-stub&os=win&lang=en-US&attribution_code=`+url.QueryEscape(base64Code), nil)
339+
for _, params := range []struct {
340+
AttributionCode string
341+
ExpectedCode string
342+
}{
343+
{
344+
AttributionCode: `campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.google.com`,
345+
ExpectedCode: `campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3Dwww.google.com`,
346+
},
347+
{
348+
AttributionCode: `campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=notinthewhitelist.com`,
349+
ExpectedCode: `campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3D%2528other%2529`,
350+
},
351+
} {
352+
testHook.Reset()
353+
354+
expectedCodeRegexp := regexp.MustCompile(params.ExpectedCode)
254355

255356
recorder := httptest.NewRecorder()
357+
base64Code := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(params.AttributionCode))
358+
req := httptest.NewRequest(
359+
"GET",
360+
`http://test/?product=firefox-stub&os=win&lang=en-US&attribution_code=`+url.QueryEscape(base64Code),
361+
nil,
362+
)
256363
svc.ServeHTTP(recorder, req)
257364

258365
if recorder.Code != 200 {
@@ -263,27 +370,14 @@ func TestDirectFull(t *testing.T) {
263370
if err != nil {
264371
t.Fatal("could not read body", err)
265372
}
266-
267373
if len(bodyBytes) != len(testFileBytes) {
268374
t.Error("Returned file was not the same length as the original file")
269375
}
270376

271377
if !expectedCodeRegexp.Match(bodyBytes) {
272378
t.Error("Returned file did not contain attribution code")
273379
}
274-
//if !bytes.Contains(bodyBytes, []byte(url.QueryEscape(expectedCode))) {
275-
//t.Error("Returned file did not contain attribution code")
276-
//}
277380
}
278-
279-
runTest(
280-
`campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=www.google.com`,
281-
`campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3Dwww.google.com`,
282-
)
283-
runTest(
284-
`campaign=%28not+set%29&content=%28not+set%29&medium=organic&source=notinthewhitelist.com`,
285-
`campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26dltoken%3D[\w\d-]+%26medium%3Dorganic%26source%3D%2528other%2529`,
286-
)
287381
}
288382

289383
func TestStubServiceErrorCases(t *testing.T) {

stubservice/stubhandlers/stubservice.go

+8-10
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,12 @@ func (s *stubService) ServeHTTP(w http.ResponseWriter, req *http.Request) {
4545
logrus.Fields{
4646
"log_type": "download_started",
4747
"dltoken": code.DownloadToken(),
48-
// This field should be named `client_id` but, for historical reasons, we
49-
// use `visit_id`. It'd be good to be able to rename this field but we
50-
// need to work with the Data Science team since that'd be a breaking
51-
// change.
48+
// We need to keep this field until we are sure that the consumers of
49+
// this log statement use the new field (`client_id`).
5250
//
53-
// See: https://github.com/mozilla-services/stubattribution/issues/153
51+
// See: https://github.com/mozilla-services/stubattribution/issues/155
5452
"visit_id": code.ClientID,
53+
"client_id": code.ClientID,
5554
"session_id": code.SessionID,
5655
},
5756
).Info("Download Started")
@@ -89,13 +88,12 @@ func (s *stubService) ServeHTTP(w http.ResponseWriter, req *http.Request) {
8988
logrus.Fields{
9089
"log_type": "download_finished",
9190
"dltoken": code.DownloadToken(),
92-
// This field should be named `client_id` but, for historical reasons, we
93-
// use `visit_id`. It'd be good to be able to rename this field but we
94-
// need to work with the Data Science team since that'd be a breaking
95-
// change.
91+
// We need to keep this field until we are sure that the consumers of
92+
// this log statement use the new field (`client_id`).
9693
//
97-
// See: https://github.com/mozilla-services/stubattribution/issues/153
94+
// See: https://github.com/mozilla-services/stubattribution/issues/155
9895
"visit_id": code.ClientID,
96+
"client_id": code.ClientID,
9997
"session_id": code.SessionID,
10098
},
10199
).Info("Download Finished")

0 commit comments

Comments
 (0)