-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding CMP vendor list loading mechanism & validation against the CMP…
… ID coming inside the consent. (#3) * Adding CMP vendor list loading mechanism & validation against the CMP ID coming inside the consent. * Using go install to install the testing dependencies. * Fixing error message as it's different depending on the environment we run the tests.
- Loading branch information
Showing
18 changed files
with
558 additions
and
188 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"printWidth": 120, | ||
"tabWidth": 2, | ||
"singleQuote": false, | ||
"trailingComma": "all", | ||
"arrowParens": "always" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,60 @@ | ||
package iab_tcf | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/montanaflynn/stats" | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/onsi/gomega/gmeasure" | ||
) | ||
|
||
type data struct { | ||
message string | ||
consent string | ||
result string | ||
} | ||
|
||
var _ = Describe("performance", func() { | ||
|
||
const ( | ||
times = 1000 | ||
) | ||
|
||
var ( | ||
testResults = map[string][]int64{} | ||
testData = []data{ | ||
data{ | ||
message: "v1 consent", | ||
consent: "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02", | ||
result: "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011", | ||
}, | ||
data{ | ||
message: "v2 consent", | ||
consent: "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA", | ||
result: "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101", | ||
}, | ||
experiment *gmeasure.Experiment | ||
times = gmeasure.SamplingConfig{N: 1000} | ||
testConsentV1 = data{ | ||
consent: "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02", | ||
result: "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011", | ||
} | ||
testConsentV2 = data{ | ||
consent: "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA", | ||
result: "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101", | ||
} | ||
) | ||
|
||
AfterSuite(func() { | ||
for testName, results := range testResults { | ||
p99, _ := stats.Percentile(stats.LoadRawData(results), 99) | ||
Expect(p99).Should(BeNumerically("<", 2000), testName+" shouldn't take too long.") | ||
} | ||
BeforeEach(func() { | ||
experiment = gmeasure.NewExperiment("consent speed") | ||
}) | ||
|
||
var run = func(consent string, result string) func(b Benchmarker) { | ||
return func(b Benchmarker) { | ||
runtime := b.Time("runtime", func() { | ||
Context("custom parser", func() { | ||
var run = func(consent string, expected string) func(_ int) { | ||
return func(_ int) { | ||
output, err := NewConsent(consent) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(output.GetConsentBitstring()).To(Equal(result)) | ||
}) | ||
testName := CurrentGinkgoTestDescription().TestText | ||
testResults[testName] = append(testResults[testName], runtime.Microseconds()) | ||
b.RecordValue("spent in microseconds", float64(runtime.Microseconds())) | ||
Expect(output.GetConsentBitstring()).To(Equal(expected)) | ||
} | ||
} | ||
} | ||
|
||
Context("custom parser", func() { | ||
|
||
var runCustom = func(consent string, result string) func(b Benchmarker) { | ||
return run(consent, result) | ||
var assert = func() { | ||
measurements := experiment.Get("runtime") | ||
p99, _ := stats.Percentile(stats.LoadRawData(measurements.Durations), 99) | ||
Expect(p99).Should(BeNumerically("<", 2000*time.Millisecond), "it shouldn't take too long.") | ||
} | ||
|
||
for _, data := range testData { | ||
testResults[data.message] = []int64{} | ||
Measure(data.message, runCustom(data.consent, data.result), times) | ||
} | ||
It("is fast with v1", func() { | ||
experiment.SampleDuration("runtime", run(testConsentV1.consent, testConsentV1.result), times) | ||
assert() | ||
}) | ||
|
||
It("is fast with v2", func() { | ||
experiment.SampleDuration("runtime", run(testConsentV2.consent, testConsentV2.result), times) | ||
assert() | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package cmp | ||
|
||
type Consent struct{} | ||
|
||
// ValidCMPs returns the list of valid CMPs loaded. | ||
func (c *Consent) ValidCMPs() []int { | ||
return ValidCMPs | ||
} | ||
|
||
// IsCMPListLoaded returns if the list of valid CMPs was loaded or not. | ||
func (c *Consent) IsCMPListLoaded() bool { | ||
return c.ValidCMPs() != nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package cmp_test | ||
|
||
import ( | ||
"github.com/hybridtheory/iab-tcf/cmp" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
var _ = Describe("Consent", func() { | ||
var ( | ||
testValidCMPs = []int{1, 2, 3} | ||
consent *cmp.Consent | ||
) | ||
|
||
BeforeEach(func() { | ||
consent = &cmp.Consent{} | ||
}) | ||
|
||
It("returns valid cmps", func() { | ||
cmp.ValidCMPs = testValidCMPs | ||
Expect(consent.ValidCMPs()).To(Equal(testValidCMPs)) | ||
}) | ||
|
||
Context("is list loaded", func() { | ||
It("returns false if it is not loaded", func() { | ||
cmp.ValidCMPs = nil | ||
Expect(consent.IsCMPListLoaded()).To(BeFalse()) | ||
}) | ||
|
||
It("returns true if it is loaded but empty", func() { | ||
cmp.ValidCMPs = []int{} | ||
Expect(consent.IsCMPListLoaded()).To(BeTrue()) | ||
}) | ||
|
||
It("returns true if it is properly loaded", func() { | ||
cmp.ValidCMPs = testValidCMPs | ||
Expect(consent.IsCMPListLoaded()).To(BeTrue()) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package cmp | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"golang.org/x/exp/maps" | ||
) | ||
|
||
const ( | ||
DefaultCMPVendorList = "https://cmplist.consensu.org/v2/cmp-list.json" | ||
) | ||
|
||
var ( | ||
ValidCMPs []int | ||
) | ||
|
||
// Option is the type that allows us to configure the Loader dynamically. | ||
type Option func(loader *Loader) | ||
|
||
// Loader is the type that contains the logic to load and parse a CMP JSON list. | ||
type Loader struct { | ||
URL string | ||
} | ||
|
||
// CMP contains the structure of the CMP info that comes inside the JSON | ||
type CMP struct { | ||
ID int | ||
Name string | ||
IsCommercial bool | ||
Environments []string | ||
} | ||
|
||
// WithURL allows to configure a different URL for the CMP JSON list. | ||
func WithURL(url string) Option { | ||
return func(cmp *Loader) { | ||
cmp.URL = url | ||
} | ||
} | ||
|
||
// NewLoader returns a CMP vendor list loader instance. | ||
func NewLoader(options ...Option) *Loader { | ||
loader := &Loader{ | ||
URL: DefaultCMPVendorList, | ||
} | ||
for _, option := range options { | ||
option(loader) | ||
} | ||
return loader | ||
} | ||
|
||
// Unmarshal parses the JSON vendor list into a struct so we can use them. | ||
func (loader *Loader) Unmarshal(response *http.Response) ([]CMP, error) { | ||
type Response struct { | ||
CMPS map[string]CMP `json:"cmps"` | ||
} | ||
data := Response{} | ||
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { | ||
return []CMP{}, err | ||
} | ||
return maps.Values(data.CMPS), nil | ||
} | ||
|
||
// Load is used to load the vendor list into a list of CMP information. | ||
func (loader *Loader) Load() ([]CMP, error) { | ||
response, err := http.Get(loader.URL) | ||
if err == nil { | ||
return loader.Unmarshal(response) | ||
} | ||
return []CMP{}, err | ||
} | ||
|
||
// LoadIDs loads the list of vendor CMP ids globally so we can reuse it | ||
// with subsequent calls. | ||
func (loader *Loader) LoadIDs() error { | ||
cmps, err := loader.Load() | ||
if err == nil { | ||
ValidCMPs = []int{} | ||
for _, cmp := range cmps { | ||
ValidCMPs = append(ValidCMPs, cmp.ID) | ||
} | ||
} | ||
return err | ||
} |
Oops, something went wrong.