ooni/probe-cli v3.9.0
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEc4h3qmyCnyakMcX0gLaRJ3cz2VsFAmBmEZgACgkQgLaRJ3cz 2VvP/RAAgoyC5dahE1c6DBlcWi58mzXMhoIFvhwXynozo3f+BoX99E2iSX5FuMQl f7OaFjJP3VHTXosupIKAW2gMvBPDnWQaHWmTORrxO+3rc1sayTRfj/49z+vJFvUU CmNQPQig2MEri4ArjktH/UTFUv20kjRYUocmEwvKEaijAlVWpVIzjaH31sYHQi0K RnYjrbCqq3g3BghP5eQ7njBQmgMkKgdcsO6zMuH3Z9nwsxyT6fLIa5TA8YckAzJp I2CXcpEj9JNJ44jPqcC3wxafufc2QUlrSQMCc1+kt8EMxHObYtxqq4Z3mIwvC41W WPjf4/ihwAUkyQTdqHVnJ20ZlOPq0IQEJbj6sHLsDcyZ8CD7YxNVnjvoc1i77lrl LXjiMLd6ARAiJoi2NfmWwnAgF/f32YCnwO21xHX44s8ISZC2V6g0IRl4eMAr4kEm 8ESvgjZ/LVW4hfiJREMaxZ0kzJQjT0ORrWyCUe6jJmRgFoTbbW3K40QLVHqPxuEW wfxTzLwU/NFDZGUFFAfWXgvbDdAcYQMf9JyrNRNgAIi2IY+7KhFEZ/zMN7zy9ugJ sz5ryj6mKmke49u25kX0dm0sKlzVNyIUDsqbc9XiJ18zPv2CvY67BlTiA+uVCYq2 UoEB9dV7uAYyAsuIqNZORTNdqquPEACFMl7CJthB2Ei+eqY1B+M= =HvUU -----END PGP SIGNATURE----- Merge tag 'v3.9.0' into mobile-staging ooni/probe-cli v3.9.0
This commit is contained in:
commit
d9486e3d67
1
.github/workflows/alltests.yml
vendored
1
.github/workflows/alltests.yml
vendored
|
@ -13,5 +13,4 @@ jobs:
|
|||
with:
|
||||
go-version: "1.16"
|
||||
- uses: actions/checkout@v2
|
||||
- run: go run ./internal/cmd/getresources
|
||||
- run: go test -race -tags shaping ./...
|
||||
|
|
1
.github/workflows/coverage.yml
vendored
1
.github/workflows/coverage.yml
vendored
|
@ -15,7 +15,6 @@ jobs:
|
|||
with:
|
||||
go-version: "${{ matrix.go }}"
|
||||
- uses: actions/checkout@v2
|
||||
- run: go run ./internal/cmd/getresources
|
||||
- run: go test -short -race -tags shaping -coverprofile=probe-cli.cov ./...
|
||||
- uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
|
|
1
.github/workflows/shorttests.yml
vendored
1
.github/workflows/shorttests.yml
vendored
|
@ -15,5 +15,4 @@ jobs:
|
|||
with:
|
||||
go-version: "${{ matrix.go }}"
|
||||
- uses: actions/checkout@v2
|
||||
- run: go run ./internal/cmd/getresources
|
||||
- run: go test -short -race -tags shaping ./...
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
go run ./internal/cmd/getresources
|
||||
go build -v ./internal/cmd/miniooni
|
||||
probeservices=()
|
||||
probeservices+=( "https://ps1.ooni.io" )
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/bin/sh
|
||||
set -ex
|
||||
export GOPATH=/jafar/QA/GOPATH GOCACHE=/jafar/QA/GOCACHE GO111MODULE=on
|
||||
go run ./internal/cmd/getresources
|
||||
go build -v ./internal/cmd/miniooni
|
||||
go build -v ./internal/cmd/jafar
|
||||
sudo ./QA/$1.py ./miniooni
|
||||
|
|
20
Readme.md
20
Readme.md
|
@ -24,15 +24,7 @@ Every top-level directory contains an explanatory README file.
|
|||
## Development setup
|
||||
|
||||
Be sure you have golang >= 1.16 and a C compiler (when developing for Windows, you
|
||||
need Mingw-w64 installed).
|
||||
|
||||
You need to download assets first using:
|
||||
|
||||
```bash
|
||||
go run ./internal/cmd/getresources
|
||||
```
|
||||
|
||||
Then you can build using:
|
||||
need Mingw-w64 installed). You can build using:
|
||||
|
||||
```bash
|
||||
go build -v ./cmd/ooniprobe
|
||||
|
@ -80,10 +72,6 @@ go get -u -v ./... && go mod tidy
|
|||
|
||||
## Releasing
|
||||
|
||||
1. update binary data as described above;
|
||||
|
||||
2. update `internal/version/version.go`;
|
||||
|
||||
3. make sure you have updated dependencies;
|
||||
|
||||
4. run `./build.sh release` and follow instructions.
|
||||
Create an issue according to [the routine release template](
|
||||
https://github.com/ooni/probe/blob/master/.github/ISSUE_TEMPLATE/routine-sprint-releases.md)
|
||||
and perform any item inside the check-list.
|
||||
|
|
|
@ -28,5 +28,4 @@ export PATH=$(go env GOPATH)/bin:$PATH
|
|||
go get -u golang.org/x/mobile/cmd/gomobile
|
||||
gomobile init
|
||||
output=MOBILE/android/oonimkall.aar
|
||||
go run ./internal/cmd/getresources
|
||||
gomobile bind -target=android -o $output -ldflags="-s -w" ./pkg/oonimkall
|
||||
|
|
|
@ -6,5 +6,4 @@ export PATH=$(go env GOPATH)/bin:$PATH
|
|||
go get -u golang.org/x/mobile/cmd/gomobile
|
||||
gomobile init
|
||||
output=MOBILE/ios/oonimkall.framework
|
||||
go run ./internal/cmd/getresources
|
||||
gomobile bind -target=ios -o $output -ldflags="-s -w" ./pkg/oonimkall
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
set -e
|
||||
case $1 in
|
||||
macos|darwin)
|
||||
go run ./internal/cmd/getresources
|
||||
export GOOS=darwin GOARCH=amd64
|
||||
go build -o ./CLI/darwin/amd64 -ldflags="-s -w" ./internal/cmd/miniooni
|
||||
echo "Binary ready at ./CLI/darwin/amd64/miniooni";;
|
||||
linux)
|
||||
go run ./internal/cmd/getresources
|
||||
export GOOS=linux GOARCH=386
|
||||
go build -o ./CLI/linux/386 -tags netgo -ldflags='-s -w -extldflags "-static"' ./internal/cmd/miniooni
|
||||
echo "Binary ready at ./CLI/linux/386/miniooni"
|
||||
|
@ -21,7 +19,6 @@ case $1 in
|
|||
go build -o ./CLI/linux/arm64 -tags netgo -ldflags='-s -w -extldflags "-static"' ./internal/cmd/miniooni
|
||||
echo "Binary ready at ./CLI/linux/arm64/miniooni";;
|
||||
windows)
|
||||
go run ./internal/cmd/getresources
|
||||
export GOOS=windows GOARCH=386
|
||||
go build -o ./CLI/windows/386 -ldflags="-s -w" ./internal/cmd/miniooni
|
||||
echo "Binary ready at ./CLI/windows/386/miniooni.exe"
|
||||
|
|
5
build.sh
5
build.sh
|
@ -12,7 +12,6 @@ case $1 in
|
|||
;;
|
||||
|
||||
windows_amd64)
|
||||
go run ./internal/cmd/getresources
|
||||
# Note! This assumes we've installed the mingw-w64 compiler.
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \
|
||||
go build -ldflags='-s -w' ./cmd/ooniprobe
|
||||
|
@ -23,7 +22,6 @@ case $1 in
|
|||
;;
|
||||
|
||||
windows_386)
|
||||
go run ./internal/cmd/getresources
|
||||
# Note! This assumes we've installed the mingw-w64 compiler.
|
||||
GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc \
|
||||
go build -ldflags='-s -w' ./cmd/ooniprobe
|
||||
|
@ -40,7 +38,6 @@ case $1 in
|
|||
;;
|
||||
|
||||
linux_amd64)
|
||||
go run ./internal/cmd/getresources
|
||||
docker pull --platform linux/amd64 golang:1.16-alpine
|
||||
docker run --platform linux/amd64 -v`pwd`:/ooni -w/ooni golang:1.16-alpine ./build.sh _alpine
|
||||
tar -cvzf ooniprobe_${v}_linux_amd64.tar.gz LICENSE.md Readme.md ooniprobe
|
||||
|
@ -48,7 +45,6 @@ case $1 in
|
|||
;;
|
||||
|
||||
linux_386)
|
||||
go run ./internal/cmd/getresources
|
||||
docker pull --platform linux/386 golang:1.16-alpine
|
||||
docker run --platform linux/386 -v`pwd`:/ooni -w/ooni golang:1.16-alpine ./build.sh _alpine
|
||||
tar -cvzf ooniprobe_${v}_linux_386.tar.gz LICENSE.md Readme.md ooniprobe
|
||||
|
@ -64,7 +60,6 @@ case $1 in
|
|||
|
||||
macos|darwin)
|
||||
set -x
|
||||
go run ./internal/cmd/getresources
|
||||
# Note! The following line _assumes_ you have a working C compiler. If you
|
||||
# have Xcode command line tools installed, you are fine.
|
||||
go build -ldflags='-s -w' ./cmd/ooniprobe
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package run
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/fatih/color"
|
||||
|
@ -28,19 +26,23 @@ func init() {
|
|||
log.WithError(err).Error("failed to perform onboarding")
|
||||
return err
|
||||
}
|
||||
if *noCollector == true {
|
||||
if *noCollector {
|
||||
probe.Config().Sharing.UploadResults = false
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
functionalRun := func(pred func(name string, gr nettests.Group) bool) error {
|
||||
functionalRun := func(runType string, pred func(name string, gr nettests.Group) bool) error {
|
||||
for name, group := range nettests.All {
|
||||
if pred(name, group) != true {
|
||||
if !pred(name, group) {
|
||||
continue
|
||||
}
|
||||
log.Infof("Running %s tests", color.BlueString(name))
|
||||
conf := nettests.RunGroupConfig{GroupName: name, Probe: probe}
|
||||
conf := nettests.RunGroupConfig{
|
||||
GroupName: name,
|
||||
Probe: probe,
|
||||
RunType: runType,
|
||||
}
|
||||
if err := nettests.RunGroup(conf); err != nil {
|
||||
log.WithError(err).Errorf("failed to run %s", name)
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ func init() {
|
|||
|
||||
genRunWithGroupName := func(targetName string) func(*kingpin.ParseContext) error {
|
||||
return func(*kingpin.ParseContext) error {
|
||||
return functionalRun(func(groupName string, gr nettests.Group) bool {
|
||||
return functionalRun("manual", func(groupName string, gr nettests.Group) bool {
|
||||
return groupName == targetName
|
||||
})
|
||||
}
|
||||
|
@ -66,6 +68,7 @@ func init() {
|
|||
Probe: probe,
|
||||
InputFiles: *inputFile,
|
||||
Inputs: *input,
|
||||
RunType: "manual",
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -77,22 +80,14 @@ func init() {
|
|||
|
||||
unattendedCmd := cmd.Command("unattended", "")
|
||||
unattendedCmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
// Until we have enabled the check-in API we're called every
|
||||
// hour on darwin and we need to self throttle.
|
||||
// TODO(bassosimone): switch to check-in and remove this hack.
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "windows":
|
||||
const veryFew = 10
|
||||
probe.Config().Nettests.WebsitesURLLimit = veryFew
|
||||
}
|
||||
return functionalRun(func(name string, gr nettests.Group) bool {
|
||||
return gr.UnattendedOK == true
|
||||
return functionalRun("timed", func(name string, gr nettests.Group) bool {
|
||||
return gr.UnattendedOK
|
||||
})
|
||||
})
|
||||
|
||||
allCmd := cmd.Command("all", "").Default()
|
||||
allCmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
return functionalRun(func(name string, gr nettests.Group) bool {
|
||||
return functionalRun("manual", func(name string, gr nettests.Group) bool {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,38 +1,5 @@
|
|||
package config
|
||||
|
||||
var websiteCategories = []string{
|
||||
"ALDR",
|
||||
"ANON",
|
||||
"COMM",
|
||||
"COMT",
|
||||
"CTRL",
|
||||
"CULTR",
|
||||
"DATE",
|
||||
"ECON",
|
||||
"ENV",
|
||||
"FILE",
|
||||
"GAME",
|
||||
"GMB",
|
||||
"GOVT",
|
||||
"GRP",
|
||||
"HACK",
|
||||
"HATE",
|
||||
"HOST",
|
||||
"HUMR",
|
||||
"IGO",
|
||||
"LGBT",
|
||||
"MILX",
|
||||
"MMED",
|
||||
"NEWS",
|
||||
"POLR",
|
||||
"PORN",
|
||||
"PROV",
|
||||
"PUBH",
|
||||
"REL",
|
||||
"SRCH",
|
||||
"XED",
|
||||
}
|
||||
|
||||
// Sharing settings
|
||||
type Sharing struct {
|
||||
UploadResults bool `json:"upload_results"`
|
||||
|
@ -45,6 +12,7 @@ type Advanced struct {
|
|||
|
||||
// Nettests related settings
|
||||
type Nettests struct {
|
||||
WebsitesMaxRuntime int64 `json:"websites_max_runtime"`
|
||||
WebsitesURLLimit int64 `json:"websites_url_limit"`
|
||||
WebsitesEnabledCategoryCodes []string `json:"websites_enabled_category_codes"`
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"upload_results": true
|
||||
},
|
||||
"nettests": {
|
||||
"websites_url_limit": 0
|
||||
"websites_max_runtime": 0
|
||||
},
|
||||
"advanced": {
|
||||
"send_crash_reports": true
|
||||
|
|
|
@ -9,8 +9,17 @@ import (
|
|||
"github.com/apex/log"
|
||||
)
|
||||
|
||||
// Default handler outputting to stderr.
|
||||
var Default = New(os.Stderr)
|
||||
// Default handler outputting to stdout. We want to emit the batch
|
||||
// output on the standard output, for two reasons:
|
||||
//
|
||||
// 1. because third party libraries MAY log on the stderr and
|
||||
// their logs are most likely not JSON;
|
||||
//
|
||||
// 2. because this enables piping to `jq` or other tools in
|
||||
// a much more natural way than when emitting on stderr.
|
||||
//
|
||||
// See https://github.com/ooni/probe/issues/1384.
|
||||
var Default = New(os.Stdout)
|
||||
|
||||
// Handler implementation.
|
||||
type Handler struct {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
|
||||
)
|
||||
|
||||
// Default handler outputting to stderr.
|
||||
// Default handler outputting to stdout.
|
||||
var Default = New(os.Stdout)
|
||||
|
||||
// start time.
|
||||
|
|
|
@ -37,6 +37,7 @@ var All = map[string]Group{
|
|||
FacebookMessenger{},
|
||||
Telegram{},
|
||||
WhatsApp{},
|
||||
Signal{},
|
||||
},
|
||||
UnattendedOK: true,
|
||||
},
|
||||
|
@ -54,7 +55,6 @@ var All = map[string]Group{
|
|||
Nettests: []Nettest{
|
||||
DNSCheck{},
|
||||
STUNReachability{},
|
||||
Signal{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ type Controller struct {
|
|||
// using the command line using the --input flag.
|
||||
Inputs []string
|
||||
|
||||
// RunType contains the run_type hint for the CheckIn API. If
|
||||
// not set, the underlying code defaults to "timed".
|
||||
RunType string
|
||||
|
||||
// numInputs is the total number of inputs
|
||||
numInputs int
|
||||
|
||||
|
@ -111,10 +115,20 @@ func (c *Controller) Run(builder *engine.ExperimentBuilder, inputs []string) err
|
|||
}
|
||||
}
|
||||
|
||||
c.ntStartTime = time.Now()
|
||||
maxRuntime := time.Duration(c.Probe.Config().Nettests.WebsitesMaxRuntime) * time.Second
|
||||
if c.RunType == "timed" && maxRuntime > 0 {
|
||||
log.Debug("disabling maxRuntime when running in the background")
|
||||
maxRuntime = 0
|
||||
}
|
||||
start := time.Now()
|
||||
c.ntStartTime = start
|
||||
for idx, input := range inputs {
|
||||
if c.Probe.IsTerminated() == true {
|
||||
log.Debug("isTerminated == true, breaking the input loop")
|
||||
if c.Probe.IsTerminated() {
|
||||
log.Info("user requested us to terminate using Ctrl-C")
|
||||
break
|
||||
}
|
||||
if maxRuntime > 0 && time.Since(start) > maxRuntime {
|
||||
log.Info("exceeded maximum runtime")
|
||||
break
|
||||
}
|
||||
c.curInputIdx = idx // allow for precise progress
|
||||
|
@ -166,7 +180,7 @@ func (c *Controller) Run(builder *engine.ExperimentBuilder, inputs []string) err
|
|||
}
|
||||
}
|
||||
// We only save the measurement to disk if we failed to upload the measurement
|
||||
if saveToDisk == true {
|
||||
if saveToDisk {
|
||||
if err := exp.SaveMeasurement(measurement, msmt.MeasurementFilePath.String); err != nil {
|
||||
return errors.Wrap(err, "failed to save measurement on disk")
|
||||
}
|
||||
|
@ -198,6 +212,18 @@ func (c *Controller) Run(builder *engine.ExperimentBuilder, inputs []string) err
|
|||
|
||||
// OnProgress should be called when a new progress event is available.
|
||||
func (c *Controller) OnProgress(perc float64, msg string) {
|
||||
// when we have maxRuntime, honor it
|
||||
maxRuntime := time.Duration(c.Probe.Config().Nettests.WebsitesMaxRuntime) * time.Second
|
||||
if c.RunType == "manual" && maxRuntime > 0 {
|
||||
elapsed := time.Since(c.ntStartTime)
|
||||
perc = float64(elapsed) / float64(maxRuntime)
|
||||
eta := maxRuntime.Seconds() - elapsed.Seconds()
|
||||
log.Debugf("OnProgress: %f - %s", perc, msg)
|
||||
key := fmt.Sprintf("%T", c.nt)
|
||||
output.Progress(key, perc, eta, msg)
|
||||
return
|
||||
}
|
||||
// otherwise estimate the ETA
|
||||
log.Debugf("OnProgress: %f - %s", perc, msg)
|
||||
var eta float64
|
||||
eta = -1.0
|
||||
|
@ -207,7 +233,7 @@ func (c *Controller) OnProgress(perc float64, msg string) {
|
|||
step := 1.0 / float64(c.numInputs)
|
||||
perc = floor + perc*step
|
||||
if c.curInputIdx > 0 {
|
||||
eta = (time.Now().Sub(c.ntStartTime).Seconds() / float64(c.curInputIdx)) * float64(c.numInputs-c.curInputIdx)
|
||||
eta = (time.Since(c.ntStartTime).Seconds() / float64(c.curInputIdx)) * float64(c.numInputs-c.curInputIdx)
|
||||
}
|
||||
}
|
||||
if c.ntCount > 0 {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package nettests
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
|
@ -10,14 +13,46 @@ import (
|
|||
// RunGroupConfig contains the settings for running a nettest group.
|
||||
type RunGroupConfig struct {
|
||||
GroupName string
|
||||
Probe *ooni.Probe
|
||||
InputFiles []string
|
||||
Inputs []string
|
||||
Probe *ooni.Probe
|
||||
RunType string // hint for check-in API
|
||||
}
|
||||
|
||||
const websitesURLLimitRemoved = `WARNING: CONFIGURATION CHANGE REQUIRED:
|
||||
|
||||
* Since ooniprobe 3.9.0, websites_url_limit has been replaced
|
||||
by websites_max_runtime in the configuration
|
||||
|
||||
* To silence this warning either set websites_url_limit to zero or
|
||||
replace it with websites_max_runtime
|
||||
|
||||
* For the rest of 2021, we will automatically convert websites_url_limit
|
||||
to websites_max_runtime (if the latter is not already set)
|
||||
|
||||
* We will consider that each URL in websites_url_limit takes five
|
||||
seconds to run and thus calculate websites_max_runtime
|
||||
|
||||
* Since 2022, we will start silently ignoring websites_url_limit
|
||||
`
|
||||
|
||||
var deprecationWarningOnce sync.Once
|
||||
|
||||
// RunGroup runs a group of nettests according to the specified config.
|
||||
func RunGroup(config RunGroupConfig) error {
|
||||
if config.Probe.IsTerminated() == true {
|
||||
if config.Probe.Config().Nettests.WebsitesURLLimit > 0 {
|
||||
if config.Probe.Config().Nettests.WebsitesMaxRuntime <= 0 {
|
||||
limit := config.Probe.Config().Nettests.WebsitesURLLimit
|
||||
maxRuntime := 5 * limit
|
||||
config.Probe.Config().Nettests.WebsitesMaxRuntime = maxRuntime
|
||||
}
|
||||
deprecationWarningOnce.Do(func() {
|
||||
log.Warn(websitesURLLimitRemoved)
|
||||
time.Sleep(30 * time.Second)
|
||||
})
|
||||
}
|
||||
|
||||
if config.Probe.IsTerminated() {
|
||||
log.Debugf("context is terminated, stopping runNettestGroup early")
|
||||
return nil
|
||||
}
|
||||
|
@ -61,7 +96,7 @@ func RunGroup(config RunGroupConfig) error {
|
|||
config.Probe.ListenForSignals()
|
||||
config.Probe.MaybeListenForStdinClosed()
|
||||
for i, nt := range group.Nettests {
|
||||
if config.Probe.IsTerminated() == true {
|
||||
if config.Probe.IsTerminated() {
|
||||
log.Debugf("context is terminated, stopping group.Nettests early")
|
||||
break
|
||||
}
|
||||
|
@ -69,6 +104,7 @@ func RunGroup(config RunGroupConfig) error {
|
|||
ctl := NewController(nt, config.Probe, result, sess)
|
||||
ctl.InputFiles = config.InputFiles
|
||||
ctl.Inputs = config.Inputs
|
||||
ctl.RunType = config.RunType
|
||||
ctl.SetNettestIndex(i, len(group.Nettests))
|
||||
if err = nt.Run(ctl); err != nil {
|
||||
log.WithError(err).Errorf("Failed to run %s", group.Label)
|
||||
|
|
|
@ -6,23 +6,59 @@ import (
|
|||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
|
||||
engine "github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
func lookupURLs(ctl *Controller, limit int64, categories []string) ([]string, map[int64]int64, error) {
|
||||
inputloader := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputOrQueryTestLists,
|
||||
Session: ctl.Session,
|
||||
SourceFiles: ctl.InputFiles,
|
||||
StaticInputs: ctl.Inputs,
|
||||
URLCategories: categories,
|
||||
URLLimit: limit,
|
||||
})
|
||||
// preventMistakes makes the code more robust with respect to any possible
|
||||
// integration issue where the backend returns to us URLs that don't
|
||||
// belong to the category codes we requested.
|
||||
func preventMistakes(input []model.URLInfo, categories []string) (output []model.URLInfo) {
|
||||
if len(categories) <= 0 {
|
||||
return input
|
||||
}
|
||||
for _, entry := range input {
|
||||
var found bool
|
||||
for _, cat := range categories {
|
||||
if entry.CategoryCode == cat {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Warnf("URL %+v not in %+v; skipping", entry, categories)
|
||||
continue
|
||||
}
|
||||
output = append(output, entry)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lookupURLs(ctl *Controller, categories []string) ([]string, map[int64]int64, error) {
|
||||
inputloader := &engine.InputLoader{
|
||||
CheckInConfig: &model.CheckInConfig{
|
||||
// Setting Charging and OnWiFi to true causes the CheckIn
|
||||
// API to return to us as much URL as possible with the
|
||||
// given RunType hint.
|
||||
Charging: true,
|
||||
OnWiFi: true,
|
||||
RunType: ctl.RunType,
|
||||
WebConnectivity: model.CheckInConfigWebConnectivity{
|
||||
CategoryCodes: categories,
|
||||
},
|
||||
},
|
||||
InputPolicy: engine.InputOrQueryBackend,
|
||||
Session: ctl.Session,
|
||||
SourceFiles: ctl.InputFiles,
|
||||
StaticInputs: ctl.Inputs,
|
||||
}
|
||||
log.Infof("Calling CheckIn API with %s runType", ctl.RunType)
|
||||
testlist, err := inputloader.Load(context.Background())
|
||||
var urls []string
|
||||
urlIDMap := make(map[int64]int64)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
testlist = preventMistakes(testlist, categories)
|
||||
var urls []string
|
||||
urlIDMap := make(map[int64]int64)
|
||||
for idx, url := range testlist {
|
||||
log.Debugf("Going over URL %d", idx)
|
||||
urlID, err := database.CreateOrUpdateURL(
|
||||
|
@ -40,13 +76,12 @@ func lookupURLs(ctl *Controller, limit int64, categories []string) ([]string, ma
|
|||
}
|
||||
|
||||
// WebConnectivity test implementation
|
||||
type WebConnectivity struct {
|
||||
}
|
||||
type WebConnectivity struct{}
|
||||
|
||||
// Run starts the test
|
||||
func (n WebConnectivity) Run(ctl *Controller) error {
|
||||
log.Debugf("Enabled category codes are the following %v", ctl.Probe.Config().Nettests.WebsitesEnabledCategoryCodes)
|
||||
urls, urlIDMap, err := lookupURLs(ctl, ctl.Probe.Config().Nettests.WebsitesURLLimit, ctl.Probe.Config().Nettests.WebsitesEnabledCategoryCodes)
|
||||
urls, urlIDMap, err := lookupURLs(ctl, ctl.Probe.Config().Nettests.WebsitesEnabledCategoryCodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
77
cmd/ooniprobe/internal/nettests/web_connectivity_test.go
Normal file
77
cmd/ooniprobe/internal/nettests/web_connectivity_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package nettests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
func TestPreventMistakesWithCategories(t *testing.T) {
|
||||
input := []model.URLInfo{{
|
||||
CategoryCode: "NEWS",
|
||||
URL: "https://repubblica.it/",
|
||||
CountryCode: "IT",
|
||||
}, {
|
||||
CategoryCode: "HACK",
|
||||
URL: "https://2600.com",
|
||||
CountryCode: "XX",
|
||||
}, {
|
||||
CategoryCode: "FILE",
|
||||
URL: "https://addons.mozilla.org/",
|
||||
CountryCode: "XX",
|
||||
}}
|
||||
desired := []model.URLInfo{{
|
||||
CategoryCode: "NEWS",
|
||||
URL: "https://repubblica.it/",
|
||||
CountryCode: "IT",
|
||||
}, {
|
||||
CategoryCode: "FILE",
|
||||
URL: "https://addons.mozilla.org/",
|
||||
CountryCode: "XX",
|
||||
}}
|
||||
output := preventMistakes(input, []string{"NEWS", "FILE"})
|
||||
if diff := cmp.Diff(desired, output); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreventMistakesWithoutCategoriesAndNil(t *testing.T) {
|
||||
input := []model.URLInfo{{
|
||||
CategoryCode: "NEWS",
|
||||
URL: "https://repubblica.it/",
|
||||
CountryCode: "IT",
|
||||
}, {
|
||||
CategoryCode: "HACK",
|
||||
URL: "https://2600.com",
|
||||
CountryCode: "XX",
|
||||
}, {
|
||||
CategoryCode: "FILE",
|
||||
URL: "https://addons.mozilla.org/",
|
||||
CountryCode: "XX",
|
||||
}}
|
||||
output := preventMistakes(input, nil)
|
||||
if diff := cmp.Diff(input, output); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreventMistakesWithoutCategoriesAndEmpty(t *testing.T) {
|
||||
input := []model.URLInfo{{
|
||||
CategoryCode: "NEWS",
|
||||
URL: "https://repubblica.it/",
|
||||
CountryCode: "IT",
|
||||
}, {
|
||||
CategoryCode: "HACK",
|
||||
URL: "https://2600.com",
|
||||
CountryCode: "XX",
|
||||
}, {
|
||||
CategoryCode: "FILE",
|
||||
URL: "https://addons.mozilla.org/",
|
||||
CountryCode: "XX",
|
||||
}}
|
||||
output := preventMistakes(input, []string{})
|
||||
if diff := cmp.Diff(input, output); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"upload_results": true
|
||||
},
|
||||
"nettests": {
|
||||
"websites_url_limit": 0
|
||||
"websites_max_runtime": 0
|
||||
},
|
||||
"advanced": {
|
||||
"send_crash_reports": true
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/enginex"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
|
||||
engine "github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/assetsdir"
|
||||
"github.com/pkg/errors"
|
||||
"upper.io/db.v3/lib/sqlbuilder"
|
||||
)
|
||||
|
@ -177,6 +178,14 @@ func (p *Probe) Init(softwareName, softwareVersion string) error {
|
|||
}
|
||||
p.db = db
|
||||
|
||||
// We cleanup the assets files used by versions of ooniprobe
|
||||
// older than v3.9.0, where we started embedding the assets
|
||||
// into the binary and use that directly. This cleanup doesn't
|
||||
// remove the whole directory but only known files inside it
|
||||
// and then the directory itself, if empty. We explicitly discard
|
||||
// the return value as it does not matter to us here.
|
||||
_, _ = assetsdir.Cleanup(utils.AssetsDir(p.home))
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "ooni")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating TempDir")
|
||||
|
@ -199,7 +208,6 @@ func (p *Probe) NewSession() (*engine.Session, error) {
|
|||
return nil, errors.Wrap(err, "creating engine's kvstore")
|
||||
}
|
||||
return engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: utils.AssetsDir(p.home),
|
||||
KVStore: kvstore,
|
||||
Logger: enginex.Logger,
|
||||
SoftwareName: p.softwareName,
|
||||
|
|
|
@ -39,7 +39,7 @@ func EscapeAwareRuneCountInString(s string) int {
|
|||
return n
|
||||
}
|
||||
|
||||
// RightPadd adds right padding in from of a string
|
||||
// RightPad adds right padding in from of a string
|
||||
func RightPad(str string, length int) string {
|
||||
c := length - EscapeAwareRuneCountInString(str)
|
||||
if c < 0 {
|
||||
|
|
2
cmd/ooniprobe/testdata/testing-config.json
vendored
2
cmd/ooniprobe/testdata/testing-config.json
vendored
|
@ -5,7 +5,7 @@
|
|||
"upload_results": true
|
||||
},
|
||||
"nettests": {
|
||||
"websites_url_limit": 10
|
||||
"websites_max_runtime": 15
|
||||
},
|
||||
"advanced": {
|
||||
"send_crash_reports": true
|
||||
|
|
2
debian/ooniprobe.conf.disabled
vendored
2
debian/ooniprobe.conf.disabled
vendored
|
@ -6,7 +6,7 @@
|
|||
"upload_results": true
|
||||
},
|
||||
"nettests": {
|
||||
"websites_url_limit": 0,
|
||||
"websites_max_runtime": 0,
|
||||
"websites_enabled_category_codes": null
|
||||
},
|
||||
"advanced": {
|
||||
|
|
19
go.mod
19
go.mod
|
@ -11,7 +11,6 @@ require (
|
|||
github.com/cretz/bine v0.1.0
|
||||
github.com/dchest/siphash v1.2.2 // indirect
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/martian/v3 v3.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
|
@ -19,29 +18,27 @@ require (
|
|||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hexops/gotextdiff v1.0.3
|
||||
github.com/iancoleman/strcase v0.1.3
|
||||
github.com/lucas-clemente/quic-go v0.19.3
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.2 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.20.0
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mattn/go-sqlite3 v1.14.6 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.40
|
||||
github.com/miekg/dns v1.1.41
|
||||
github.com/montanaflynn/stats v0.6.5
|
||||
github.com/ooni/psiphon v0.5.0
|
||||
github.com/ooni/probe-assets v0.0.0-20210401100648-90ed7b6dff90
|
||||
github.com/ooni/psiphon v0.6.0
|
||||
github.com/oschwald/geoip2-golang v1.5.0
|
||||
github.com/pborman/getopt/v2 v2.1.0
|
||||
github.com/pion/stun v0.3.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rogpeppe/go-internal v1.7.0
|
||||
github.com/rogpeppe/go-internal v1.8.0
|
||||
github.com/rubenv/sql-migrate v0.0.0-20210215143335-f84234893558
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
gitlab.com/yawning/obfs4.git v0.0.0-20201217005658-f638c33f6c6f
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
|
||||
upper.io/db.v3 v3.8.0+incompatible
|
||||
|
|
67
go.sum
67
go.sum
|
@ -139,13 +139,12 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V
|
|||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -154,10 +153,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
|
|||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
|
@ -165,7 +161,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
|||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
|
@ -262,19 +257,18 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
|||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4=
|
||||
github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8=
|
||||
github.com/lucas-clemente/quic-go v0.20.0 h1:FSU3YN5VnLafHR27Ejs1r1CYMS7XMyIVDzRewkDLNBw=
|
||||
github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.2 h1:KLXnVazsIS+EhrEqXqg0NyQZ2rwxkaSaNxMFc1krFIA=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.2/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
|
@ -298,8 +292,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
|
|||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
@ -341,8 +335,10 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/ooni/psiphon v0.5.0 h1:DAKZ66tmh4r6uop4yfSiQDObv7yNGb0j1jY+xeft6h4=
|
||||
github.com/ooni/psiphon v0.5.0/go.mod h1:i1v6JweJtxDKaI0i1aEw2/Fr/CUi5BoQ75GYz5KmKwU=
|
||||
github.com/ooni/probe-assets v0.0.0-20210401100648-90ed7b6dff90 h1:G7urihGBD88p5iU1bg7cFAqX/38qIPZH03wCTmyUAXI=
|
||||
github.com/ooni/probe-assets v0.0.0-20210401100648-90ed7b6dff90/go.mod h1:N0PyNM3aadlYDDCFXAPzs54HC54+MZA/4/xnCtd9EAo=
|
||||
github.com/ooni/psiphon v0.6.0 h1:TWXFSlXBOFUwfGblLKaLXSoI7UL6dreoPXHW2uQTHlc=
|
||||
github.com/ooni/psiphon v0.6.0/go.mod h1:i1v6JweJtxDKaI0i1aEw2/Fr/CUi5BoQ75GYz5KmKwU=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
|
@ -368,6 +364,7 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
|
|||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -402,8 +399,8 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.7.0 h1:3qqXGV8nn7GJT65debw77Dzrx9sfWYgP0DDo7xcMFRk=
|
||||
github.com/rogpeppe/go-internal v1.7.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20210215143335-f84234893558 h1:o8N+eY3HGAzZ+5sXNdcbCVOHW3NOksmKeEOuygusmr8=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20210215143335-f84234893558/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
|
@ -521,8 +518,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -532,6 +529,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -553,11 +551,11 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5 h1:zuP3axpB9rV3xH0EA7n3/gCrNPZm2SRl0l4mVH2BRj4=
|
||||
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -569,8 +567,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -593,7 +591,6 @@ golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -604,12 +601,12 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
|
||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -634,7 +631,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -660,7 +657,6 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
|
|||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
|
@ -672,17 +668,12 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
|||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
@ -719,8 +710,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
|
|
|
@ -2,50 +2,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/resources"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for name, ri := range resources.All {
|
||||
if err := getit(name, &ri); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getit(name string, ri *resources.ResourceInfo) error {
|
||||
workDir := filepath.Join("internal", "engine", "resourcesmanager")
|
||||
URL, err := url.Parse(resources.BaseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
URL.Path = ri.URLPath
|
||||
log.Println("fetching", URL.String())
|
||||
resp, err := http.Get(URL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("http request failed")
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checksum := fmt.Sprintf("%x", sha256.Sum256(data))
|
||||
if checksum != ri.GzSHA256 {
|
||||
return errors.New("sha256 mismatch")
|
||||
}
|
||||
fullpath := filepath.Join(workDir, name+".gz")
|
||||
return ioutil.WriteFile(fullpath, data, 0644)
|
||||
log.Printf("This command is no longer needed. We will keep it into")
|
||||
log.Printf("the repository until the end of 2021, so you have\n")
|
||||
log.Printf("time to adjust your build workflow.\n")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
This directory contains the source code of a simple CLI client that we
|
||||
use for research as well as for running QA scripts. We designed this tool
|
||||
to have a CLI similar to MK and OONI Probe v2.x to ease running Jafar
|
||||
scripts that check whether these tools behave similarly.
|
||||
|
||||
See also libminiooni.
|
||||
scripts that check whether these tools behave similarly. Perfect backwards
|
||||
compatibility was not a design goal for miniooni. Rather, we aimed to
|
||||
have as little conflict as possible, such that we can run side-by-side
|
||||
QA checks.
|
||||
|
|
|
@ -1,16 +1,4 @@
|
|||
// Package libminiooni implements the cmd/miniooni CLI. Miniooni is our
|
||||
// experimental client used for research and QA testing.
|
||||
//
|
||||
// This CLI has CLI options that do not conflict with Measurement Kit
|
||||
// v0.10.x CLI options. There are some options conflict with the legacy
|
||||
// OONI Probe CLI options. Perfect backwards compatibility is not a
|
||||
// design goal for miniooni. Rather, we aim to have as little conflict
|
||||
// as possible such that we can run side by side QA checks.
|
||||
//
|
||||
// We extracted this package from cmd/miniooni to allow us to further
|
||||
// integrate the miniooni CLI into other binaries (see for example the
|
||||
// code at github.com/bassosimone/aladdin).
|
||||
package libminiooni
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -30,6 +18,7 @@ import (
|
|||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/humanizex"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/legacy/assetsdir"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor"
|
||||
"github.com/ooni/probe-cli/v3/internal/version"
|
||||
|
@ -44,6 +33,7 @@ type Options struct {
|
|||
Inputs []string
|
||||
InputFilePaths []string
|
||||
Limit int64
|
||||
MaxRuntime int64
|
||||
NoJSON bool
|
||||
NoCollector bool
|
||||
ProbeServicesURL string
|
||||
|
@ -93,6 +83,10 @@ func init() {
|
|||
&globalOptions.Limit, "limit", 0,
|
||||
"Limit the number of URLs tested by Web Connectivity", "N",
|
||||
)
|
||||
getopt.FlagLong(
|
||||
&globalOptions.MaxRuntime, "max-runtime", 0,
|
||||
"Maximum runtime in seconds when looping over a list of inputs (zero means infinite)", "N",
|
||||
)
|
||||
getopt.FlagLong(
|
||||
&globalOptions.NoJSON, "no-json", 'N', "Disable writing to disk",
|
||||
)
|
||||
|
@ -140,10 +134,6 @@ func init() {
|
|||
)
|
||||
}
|
||||
|
||||
func fatalWithString(msg string) {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
func fatalIfFalse(cond bool, msg string) {
|
||||
if !cond {
|
||||
panic(msg)
|
||||
|
@ -274,12 +264,23 @@ func maybeWriteConsentFile(yes bool, filepath string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// limitRemoved is the text printed when the user uses --limit
|
||||
const limitRemoved = `USAGE CHANGE: The --limit option has been removed in favor of
|
||||
the --max-runtime option. Please, update your script to use --max-runtime
|
||||
instead of --limit. The argument to --max-runtime is the maximum number
|
||||
of seconds after which to stop running Web Connectivity.
|
||||
|
||||
This error message will be removed after 2021-11-01.
|
||||
`
|
||||
|
||||
// MainWithConfiguration is the miniooni main with a specific configuration
|
||||
// represented by the experiment name and the current options.
|
||||
//
|
||||
// This function will panic in case of a fatal error. It is up to you that
|
||||
// integrate this function to either handle the panic of ignore it.
|
||||
func MainWithConfiguration(experimentName string, currentOptions Options) {
|
||||
fatalIfFalse(currentOptions.Limit == 0, limitRemoved)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
extraOptions := mustMakeMap(currentOptions.ExtraOptions)
|
||||
|
@ -303,9 +304,18 @@ func MainWithConfiguration(experimentName string, currentOptions Options) {
|
|||
homeDir := gethomedir(currentOptions.HomeDir)
|
||||
fatalIfFalse(homeDir != "", "home directory is empty")
|
||||
miniooniDir := path.Join(homeDir, ".miniooni")
|
||||
err = os.MkdirAll(miniooniDir, 0700)
|
||||
fatalOnError(err, "cannot create $HOME/.miniooni directory")
|
||||
|
||||
// We cleanup the assets files used by versions of ooniprobe
|
||||
// older than v3.9.0, where we started embedding the assets
|
||||
// into the binary and use that directly. This cleanup doesn't
|
||||
// remove the whole directory but only known files inside it
|
||||
// and then the directory itself, if empty. We explicitly discard
|
||||
// the return value as it does not matter to us here.
|
||||
assetsDir := path.Join(miniooniDir, "assets")
|
||||
err = os.MkdirAll(assetsDir, 0700)
|
||||
fatalOnError(err, "cannot create assets directory")
|
||||
_, _ = assetsdir.Cleanup(assetsDir)
|
||||
|
||||
log.Debugf("miniooni state directory: %s", miniooniDir)
|
||||
|
||||
consentFile := path.Join(miniooniDir, "informed")
|
||||
|
@ -324,7 +334,6 @@ func MainWithConfiguration(experimentName string, currentOptions Options) {
|
|||
fatalOnError(err, "cannot create kvstore2 directory")
|
||||
|
||||
config := engine.SessionConfig{
|
||||
AssetsDir: assetsDir,
|
||||
KVStore: kvstore,
|
||||
Logger: logger,
|
||||
ProxyURL: proxyURL,
|
||||
|
@ -370,13 +379,17 @@ func MainWithConfiguration(experimentName string, currentOptions Options) {
|
|||
builder, err := sess.NewExperimentBuilder(experimentName)
|
||||
fatalOnError(err, "cannot create experiment builder")
|
||||
|
||||
inputLoader := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
inputLoader := &engine.InputLoader{
|
||||
CheckInConfig: &model.CheckInConfig{
|
||||
RunType: "manual",
|
||||
OnWiFi: true, // meaning: not on 4G
|
||||
Charging: true,
|
||||
},
|
||||
InputPolicy: builder.InputPolicy(),
|
||||
StaticInputs: currentOptions.Inputs,
|
||||
SourceFiles: currentOptions.InputFilePaths,
|
||||
InputPolicy: builder.InputPolicy(),
|
||||
Session: sess,
|
||||
URLLimit: currentOptions.Limit,
|
||||
})
|
||||
}
|
||||
inputs, err := inputLoader.Load(context.Background())
|
||||
fatalOnError(err, "cannot load inputs")
|
||||
|
||||
|
@ -399,29 +412,30 @@ func MainWithConfiguration(experimentName string, currentOptions Options) {
|
|||
}()
|
||||
|
||||
submitter, err := engine.NewSubmitter(ctx, engine.SubmitterConfig{
|
||||
Enabled: currentOptions.NoCollector == false,
|
||||
Enabled: !currentOptions.NoCollector,
|
||||
Session: sess,
|
||||
Logger: log.Log,
|
||||
})
|
||||
fatalOnError(err, "cannot create submitter")
|
||||
|
||||
saver, err := engine.NewSaver(engine.SaverConfig{
|
||||
Enabled: currentOptions.NoJSON == false,
|
||||
Enabled: !currentOptions.NoJSON,
|
||||
Experiment: experiment,
|
||||
FilePath: currentOptions.ReportFile,
|
||||
Logger: log.Log,
|
||||
})
|
||||
fatalOnError(err, "cannot create saver")
|
||||
|
||||
inputProcessor := engine.InputProcessor{
|
||||
inputProcessor := &engine.InputProcessor{
|
||||
Annotations: annotations,
|
||||
Experiment: &experimentWrapper{
|
||||
child: engine.NewInputProcessorExperimentWrapper(experiment),
|
||||
total: len(inputs),
|
||||
},
|
||||
Inputs: inputs,
|
||||
Options: currentOptions.ExtraOptions,
|
||||
Saver: engine.NewInputProcessorSaverWrapper(saver),
|
||||
Inputs: inputs,
|
||||
MaxRuntime: time.Duration(currentOptions.MaxRuntime) * time.Second,
|
||||
Options: currentOptions.ExtraOptions,
|
||||
Saver: engine.NewInputProcessorSaverWrapper(saver),
|
||||
Submitter: submitterWrapper{
|
||||
child: engine.NewInputProcessorSubmitterWrapper(submitter),
|
||||
},
|
12
internal/cmd/miniooni/libminiooni_test.go
Normal file
12
internal/cmd/miniooni/libminiooni_test.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
MainWithConfiguration("example", Options{
|
||||
Yes: true,
|
||||
})
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
// Command miniooni is a simple binary for research and QA purposes
|
||||
// with a CLI interface similar to MK and OONI Probe v2.x.
|
||||
//
|
||||
// See also libminiooni, which is where we implement this CLI.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/libminiooni"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -17,5 +13,5 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "%s", s)
|
||||
}
|
||||
}()
|
||||
libminiooni.Main()
|
||||
Main()
|
||||
}
|
||||
|
|
|
@ -24,9 +24,21 @@ var (
|
|||
target = flag.String("target", "", "Target URL for the test helper")
|
||||
)
|
||||
|
||||
func newhttpclient() *http.Client {
|
||||
// Use a nonstandard resolver, which is enough to work around the
|
||||
// puzzling https://github.com/ooni/probe/issues/1409 issue.
|
||||
childResolver, err := netx.NewDNSClient(
|
||||
netx.Config{Logger: log.Log}, "dot://8.8.8.8:853")
|
||||
runtimex.PanicOnError(err, "netx.NewDNSClient should not fail here")
|
||||
txp := netx.NewHTTPTransport(netx.Config{
|
||||
BaseResolver: childResolver,
|
||||
Logger: log.Log,
|
||||
})
|
||||
return &http.Client{Transport: txp}
|
||||
}
|
||||
|
||||
func init() {
|
||||
txp := netx.NewHTTPTransport(netx.Config{Logger: log.Log})
|
||||
httpClient = &http.Client{Transport: txp}
|
||||
httpClient = newhttpclient()
|
||||
resolver = netx.NewResolver(netx.Config{Logger: log.Log})
|
||||
}
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
|
|||
))
|
||||
},
|
||||
config: &httphostheader.Config{},
|
||||
inputPolicy: InputOrQueryTestLists,
|
||||
inputPolicy: InputOrQueryBackend,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -237,7 +237,7 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
|
|||
))
|
||||
},
|
||||
config: &sniblocking.Config{},
|
||||
inputPolicy: InputOrQueryTestLists,
|
||||
inputPolicy: InputOrQueryBackend,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -273,7 +273,7 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
|
|||
))
|
||||
},
|
||||
config: &tlstool.Config{},
|
||||
inputPolicy: InputOrQueryTestLists,
|
||||
inputPolicy: InputOrQueryBackend,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -309,7 +309,7 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
|
|||
))
|
||||
},
|
||||
config: &webconnectivity.Config{},
|
||||
inputPolicy: InputOrQueryTestLists,
|
||||
inputPolicy: InputOrQueryBackend,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Package engine contains the engine API
|
||||
// Package engine contains the engine API.
|
||||
package engine
|
||||
|
||||
import (
|
||||
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/geolocate"
|
||||
|
@ -17,7 +16,6 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/netx/dialer"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/httptransport"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/probeservices"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/resources"
|
||||
"github.com/ooni/probe-cli/v3/internal/version"
|
||||
)
|
||||
|
||||
|
@ -168,7 +166,6 @@ func (e *Experiment) newMeasurement(input string) *model.Measurement {
|
|||
TestStartTime: e.testStartTime,
|
||||
TestVersion: e.testVersion,
|
||||
}
|
||||
m.AddAnnotation("assets_version", strconv.FormatInt(resources.Version, 10))
|
||||
m.AddAnnotation("engine_name", "ooniprobe-engine")
|
||||
m.AddAnnotation("engine_version", version.Version)
|
||||
m.AddAnnotation("platform", platform.Name())
|
||||
|
@ -188,10 +185,7 @@ func (e *Experiment) OpenReportContext(ctx context.Context) error {
|
|||
Counter: e.byteCounter,
|
||||
},
|
||||
}
|
||||
if e.session.selectedProbeService == nil {
|
||||
return errors.New("no probe services selected")
|
||||
}
|
||||
client, err := probeservices.NewClient(e.session, *e.session.selectedProbeService)
|
||||
client, err := e.session.NewProbeServicesClient(ctx)
|
||||
if err != nil {
|
||||
e.session.logger.Debugf("%+v", err)
|
||||
return err
|
||||
|
|
|
@ -169,7 +169,7 @@ func (m *Measurer) Run(
|
|||
ResolveSaver: evsaver,
|
||||
})
|
||||
addrs, err := m.lookupHost(ctx, URL.Hostname(), resolver)
|
||||
queries := archival.NewDNSQueriesList(begin, evsaver.Read(), sess.ASNDatabasePath())
|
||||
queries := archival.NewDNSQueriesList(begin, evsaver.Read())
|
||||
tk.BootstrapFailure = archival.NewFailure(err)
|
||||
if len(queries) > 0 {
|
||||
// We get no queries in case we are resolving an IP address, since
|
||||
|
|
|
@ -222,7 +222,6 @@ func TestComputeEndpointStatsDNSIsLying(t *testing.T) {
|
|||
|
||||
func newsession(t *testing.T) model.ExperimentSession {
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "../../testdata",
|
||||
AvailableProbeServices: []model.Service{{
|
||||
Address: "https://ams-pg-test.ooni.org",
|
||||
Type: "https",
|
||||
|
|
|
@ -559,7 +559,6 @@ func TestTransactCannotReadBody(t *testing.T) {
|
|||
|
||||
func newsession(t *testing.T) model.ExperimentSession {
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "../../testdata",
|
||||
AvailableProbeServices: []model.Service{{
|
||||
Address: "https://ams-pg-test.ooni.org",
|
||||
Type: "https",
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx"
|
||||
|
@ -18,7 +17,7 @@ import (
|
|||
|
||||
const (
|
||||
testName = "riseupvpn"
|
||||
testVersion = "0.1.0"
|
||||
testVersion = "0.2.0"
|
||||
eipServiceURL = "https://api.black.riseup.net:443/3/config/eip-service.json"
|
||||
providerURL = "https://riseup.net/provider.json"
|
||||
geoServiceURL = "https://api.black.riseup.net:9001/json"
|
||||
|
@ -66,6 +65,7 @@ type TestKeys struct {
|
|||
APIStatus string `json:"api_status"`
|
||||
CACertStatus bool `json:"ca_cert_status"`
|
||||
FailingGateways []GatewayConnection `json:"failing_gateways"`
|
||||
TransportStatus map[string]string `json:"transport_status"`
|
||||
}
|
||||
|
||||
// NewTestKeys creates new riseupvpn TestKeys.
|
||||
|
@ -75,6 +75,7 @@ func NewTestKeys() *TestKeys {
|
|||
APIStatus: "ok",
|
||||
CACertStatus: true,
|
||||
FailingGateways: nil,
|
||||
TransportStatus: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +97,7 @@ func (tk *TestKeys) UpdateProviderAPITestKeys(v urlgetter.MultiOutput) {
|
|||
}
|
||||
|
||||
// AddGatewayConnectTestKeys updates the TestKeys using the given MultiOutput result of gateway connectivity testing.
|
||||
// Sets TransportStatus to "ok" if any successful TCP connection could be made
|
||||
func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transportType string) {
|
||||
tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...)
|
||||
tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...)
|
||||
|
@ -108,6 +110,29 @@ func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transport
|
|||
return
|
||||
}
|
||||
|
||||
func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount int, obfs4GatewayCount int) {
|
||||
failingOpenvpnGateways, failingObfs4Gateways := 0, 0
|
||||
for _, gw := range tk.FailingGateways {
|
||||
if gw.TransportType == "openvpn" {
|
||||
failingOpenvpnGateways++
|
||||
} else if gw.TransportType == "obfs4" {
|
||||
failingObfs4Gateways++
|
||||
}
|
||||
}
|
||||
|
||||
if failingOpenvpnGateways < openvpnGatewayCount {
|
||||
tk.TransportStatus["openvpn"] = "ok"
|
||||
} else {
|
||||
tk.TransportStatus["openvpn"] = "blocked"
|
||||
}
|
||||
|
||||
if failingObfs4Gateways < obfs4GatewayCount {
|
||||
tk.TransportStatus["obfs4"] = "ok"
|
||||
} else {
|
||||
tk.TransportStatus["obfs4"] = "blocked"
|
||||
}
|
||||
}
|
||||
|
||||
func newGatewayConnection(tcpConnect archival.TCPConnectEntry, transportType string) *GatewayConnection {
|
||||
return &GatewayConnection{
|
||||
IP: tcpConnect.IP,
|
||||
|
@ -160,30 +185,32 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
|
|||
urlgetter.RegisterExtensions(measurement)
|
||||
|
||||
caTarget := "https://black.riseup.net/ca.crt"
|
||||
caGetter := urlgetter.Getter{
|
||||
Config: m.Config.Config,
|
||||
Session: sess,
|
||||
Target: caTarget,
|
||||
}
|
||||
log.Info("Getting CA certificate; please be patient...")
|
||||
tk, err := caGetter.Get(ctx)
|
||||
testkeys.AddCACertFetchTestKeys(tk)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Getting CA certificate failed. Aborting test.")
|
||||
return nil
|
||||
}
|
||||
|
||||
certPool := netx.NewDefaultCertPool()
|
||||
if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok {
|
||||
testkeys.CACertStatus = false
|
||||
testkeys.APIStatus = "blocked"
|
||||
errorValue := "invalid_ca"
|
||||
testkeys.APIFailure = &errorValue
|
||||
return nil
|
||||
|
||||
multi := urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
|
||||
inputs := []urlgetter.MultiInput{
|
||||
{Target: caTarget, Config: urlgetter.Config{
|
||||
Method: "GET",
|
||||
FailOnHTTPError: true,
|
||||
}},
|
||||
}
|
||||
for entry := range multi.CollectOverall(ctx, inputs, 0, 50, "riseupvpn", callbacks) {
|
||||
tk := entry.TestKeys
|
||||
testkeys.AddCACertFetchTestKeys(tk)
|
||||
if tk.Failure != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok {
|
||||
testkeys.CACertStatus = false
|
||||
testkeys.APIStatus = "blocked"
|
||||
errorValue := "invalid_ca"
|
||||
testkeys.APIFailure = &errorValue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
inputs := []urlgetter.MultiInput{
|
||||
inputs = []urlgetter.MultiInput{
|
||||
|
||||
// Here we need to provide the method explicitly. See
|
||||
// https://github.com/ooni/probe-engine/issues/827.
|
||||
|
@ -203,30 +230,34 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
|
|||
FailOnHTTPError: true,
|
||||
}},
|
||||
}
|
||||
multi := urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
|
||||
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
|
||||
|
||||
for entry := range multi.CollectOverall(ctx, inputs, 0, 50, "riseupvpn", callbacks) {
|
||||
for entry := range multi.CollectOverall(ctx, inputs, 1, 50, "riseupvpn", callbacks) {
|
||||
testkeys.UpdateProviderAPITestKeys(entry)
|
||||
}
|
||||
|
||||
// test gateways now
|
||||
testkeys.TransportStatus = map[string]string{}
|
||||
gateways := parseGateways(testkeys)
|
||||
openvpnEndpoints := generateMultiInputs(gateways, "openvpn")
|
||||
obfs4Endpoints := generateMultiInputs(gateways, "obfs4")
|
||||
overallCount := len(inputs) + len(openvpnEndpoints) + len(obfs4Endpoints)
|
||||
overallCount := 1 + len(inputs) + len(openvpnEndpoints) + len(obfs4Endpoints)
|
||||
|
||||
// measure openvpn in parallel
|
||||
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
|
||||
for entry := range multi.CollectOverall(ctx, openvpnEndpoints, len(inputs), overallCount, "riseupvpn", callbacks) {
|
||||
for entry := range multi.CollectOverall(ctx, openvpnEndpoints, 1+len(inputs), overallCount, "riseupvpn", callbacks) {
|
||||
testkeys.AddGatewayConnectTestKeys(entry, "openvpn")
|
||||
}
|
||||
|
||||
// measure obfs4 in parallel
|
||||
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
|
||||
for entry := range multi.CollectOverall(ctx, obfs4Endpoints, len(inputs)+len(openvpnEndpoints), overallCount, "riseupvpn", callbacks) {
|
||||
for entry := range multi.CollectOverall(ctx, obfs4Endpoints, 1+len(inputs)+len(openvpnEndpoints), overallCount, "riseupvpn", callbacks) {
|
||||
testkeys.AddGatewayConnectTestKeys(entry, "obfs4")
|
||||
}
|
||||
|
||||
// set transport status based on gateway test results
|
||||
testkeys.updateTransportStatus(len(openvpnEndpoints), len(obfs4Endpoints))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -287,10 +318,11 @@ func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
|
|||
// Note that this structure is part of the ABI contract with probe-cli
|
||||
// therefore we should be careful when changing it.
|
||||
type SummaryKeys struct {
|
||||
APIBlocked bool `json:"api_blocked"`
|
||||
ValidCACert bool `json:"valid_ca_cert"`
|
||||
FailingGateways int `json:"failing_gateways"`
|
||||
IsAnomaly bool `json:"-"`
|
||||
APIBlocked bool `json:"api_blocked"`
|
||||
ValidCACert bool `json:"valid_ca_cert"`
|
||||
FailingGateways int `json:"failing_gateways"`
|
||||
TransportStatus map[string]string `json:"transport_status"`
|
||||
IsAnomaly bool `json:"-"`
|
||||
}
|
||||
|
||||
// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys.
|
||||
|
@ -303,7 +335,8 @@ func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, e
|
|||
sk.APIBlocked = tk.APIStatus != "ok"
|
||||
sk.ValidCACert = tk.CACertStatus
|
||||
sk.FailingGateways = len(tk.FailingGateways)
|
||||
sk.TransportStatus = tk.TransportStatus
|
||||
sk.IsAnomaly = (sk.APIBlocked == true || tk.CACertStatus == false ||
|
||||
sk.FailingGateways != 0)
|
||||
tk.TransportStatus["openvpn"] == "blocked" || tk.TransportStatus["obfs4"] == "blocked")
|
||||
return sk, nil
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ package riseupvpn_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
@ -19,36 +18,204 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/internal/mockable"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/errorx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor"
|
||||
)
|
||||
|
||||
const (
|
||||
provider = `{
|
||||
"api_uri": "https://api.black.riseup.net:443",
|
||||
"api_version": "3",
|
||||
"ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494",
|
||||
"ca_cert_uri": "https://black.riseup.net/ca.crt",
|
||||
"default_language": "en",
|
||||
"description": {
|
||||
"en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
|
||||
},
|
||||
"domain": "riseup.net",
|
||||
"enrollment_policy": "closed",
|
||||
"languages": [
|
||||
"en"
|
||||
],
|
||||
"name": {
|
||||
"en": "Riseup Networks"
|
||||
},
|
||||
"service": {
|
||||
"allow_anonymous": true,
|
||||
"allow_free": true,
|
||||
"allow_limited_bandwidth": false,
|
||||
"allow_paid": false,
|
||||
"allow_registration": false,
|
||||
"allow_unlimited_bandwidth": true,
|
||||
"bandwidth_limit": 102400,
|
||||
"default_service_level": 1,
|
||||
"levels": {
|
||||
"1": {
|
||||
"description": "Please donate.",
|
||||
"name": "free"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": [
|
||||
"openvpn"
|
||||
]
|
||||
}`
|
||||
eipservice = `{
|
||||
"gateways": [
|
||||
{
|
||||
"capabilities": {
|
||||
"adblock": false,
|
||||
"filter_dns": false,
|
||||
"limited": false,
|
||||
"transport":[
|
||||
{
|
||||
"type":"openvpn",
|
||||
"protocols":[
|
||||
"tcp"
|
||||
],
|
||||
"ports":[
|
||||
"443"
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_ips": false
|
||||
},
|
||||
"host": "test1.riseup.net",
|
||||
"ip_address": "123.456.123.456",
|
||||
"location": "paris"
|
||||
},
|
||||
{
|
||||
"capabilities": {
|
||||
"adblock": false,
|
||||
"filter_dns": false,
|
||||
"limited": false,
|
||||
"transport":[
|
||||
{
|
||||
"type":"obfs4",
|
||||
"protocols":[
|
||||
"tcp"
|
||||
],
|
||||
"ports":[
|
||||
"23042"
|
||||
],
|
||||
"options": {
|
||||
"cert": "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"iatMode": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type":"openvpn",
|
||||
"protocols":[
|
||||
"tcp"
|
||||
],
|
||||
"ports":[
|
||||
"443"
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_ips": false
|
||||
},
|
||||
"host": "test2.riseup.net",
|
||||
"ip_address": "234.345.234.345",
|
||||
"location": "seattle"
|
||||
}
|
||||
],
|
||||
"locations": {
|
||||
"paris": {
|
||||
"country_code": "FR",
|
||||
"hemisphere": "N",
|
||||
"name": "Paris",
|
||||
"timezone": "+2"
|
||||
},
|
||||
"seattle": {
|
||||
"country_code": "US",
|
||||
"hemisphere": "N",
|
||||
"name": "Seattle",
|
||||
"timezone": "-7"
|
||||
}
|
||||
},
|
||||
"openvpn_configuration": {
|
||||
"auth": "SHA1",
|
||||
"cipher": "AES-128-CBC",
|
||||
"keepalive": "10 30",
|
||||
"tls-cipher": "DHE-RSA-AES128-SHA",
|
||||
"tun-ipv6": true
|
||||
},
|
||||
"serial": 3,
|
||||
"version": 3
|
||||
}`
|
||||
geoservice = `{"ip":"51.15.0.88","cc":"NL","city":"Haarlem","lat":52.381,"lon":4.6275,"gateways":["test1.riseup.net","test2.riseup.net"]}`
|
||||
cacert = `-----BEGIN CERTIFICATE-----
|
||||
MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
|
||||
dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
|
||||
AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
|
||||
NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
|
||||
Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
|
||||
b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
|
||||
TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
|
||||
7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
|
||||
LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
|
||||
iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
|
||||
5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
|
||||
HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
|
||||
m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
|
||||
PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
|
||||
hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
|
||||
shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
|
||||
ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
|
||||
f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
|
||||
VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
|
||||
AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
|
||||
qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
|
||||
3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
|
||||
4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
|
||||
3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
|
||||
Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
|
||||
Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
|
||||
tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
|
||||
tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
|
||||
UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
|
||||
0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
|
||||
-----END CERTIFICATE-----`
|
||||
|
||||
eipserviceurl = "https://api.black.riseup.net:443/3/config/eip-service.json"
|
||||
providerurl = "https://riseup.net/provider.json"
|
||||
geoserviceurl = "https://api.black.riseup.net:9001/json"
|
||||
cacerturl = "https://black.riseup.net/ca.crt"
|
||||
openvpnurl1 = "tcpconnect://234.345.234.345:443"
|
||||
openvpnurl2 = "tcpconnect://123.456.123.456:443"
|
||||
obfs4url1 = "tcpconnect://234.345.234.345:23042"
|
||||
)
|
||||
|
||||
var RequestResponse = map[string]string{
|
||||
eipserviceurl: eipservice,
|
||||
providerurl: provider,
|
||||
geoserviceurl: geoservice,
|
||||
cacerturl: cacert,
|
||||
openvpnurl1: "",
|
||||
openvpnurl2: "",
|
||||
obfs4url1: "",
|
||||
}
|
||||
|
||||
func TestNewExperimentMeasurer(t *testing.T) {
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
if measurer.ExperimentName() != "riseupvpn" {
|
||||
t.Fatal("unexpected name")
|
||||
}
|
||||
if measurer.ExperimentVersion() != "0.1.0" {
|
||||
if measurer.ExperimentVersion() != "0.2.0" {
|
||||
t.Fatal("unexpected version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGood(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
measurement := new(model.Measurement)
|
||||
err := measurer.Run(
|
||||
context.Background(),
|
||||
&mockable.Session{
|
||||
MockableLogger: log.Log,
|
||||
},
|
||||
measurement,
|
||||
model.NewPrinterCallbacks(log.Log),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.Agent != "" {
|
||||
t.Fatal("unexpected Agent: " + tk.Agent)
|
||||
|
@ -59,21 +226,6 @@ func TestGood(t *testing.T) {
|
|||
if tk.Failure != nil {
|
||||
t.Fatal("unexpected Failure")
|
||||
}
|
||||
if len(tk.NetworkEvents) <= 0 {
|
||||
t.Fatal("no NetworkEvents?!")
|
||||
}
|
||||
if len(tk.Queries) <= 0 {
|
||||
t.Fatal("no Queries?!")
|
||||
}
|
||||
if len(tk.Requests) <= 0 {
|
||||
t.Fatal("no Requests?!")
|
||||
}
|
||||
if len(tk.TCPConnect) <= 0 {
|
||||
t.Fatal("no TCPConnect?!")
|
||||
}
|
||||
if len(tk.TLSHandshakes) <= 0 {
|
||||
t.Fatal("no TLSHandshakes?!")
|
||||
}
|
||||
if tk.APIFailure != nil {
|
||||
t.Fatal("unexpected ApiFailure")
|
||||
}
|
||||
|
@ -86,6 +238,12 @@ func TestGood(t *testing.T) {
|
|||
if tk.FailingGateways != nil {
|
||||
t.Fatal("unexpected FailingGateways value")
|
||||
}
|
||||
if tk.TransportStatus == nil {
|
||||
t.Fatal("unexpected nil TransportStatus struct ")
|
||||
}
|
||||
if tk.TransportStatus["openvpn"] != "ok" {
|
||||
t.Fatal("unexpected openvpn transport status")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateWithMixedResults tests if one operation failed
|
||||
|
@ -132,13 +290,39 @@ func TestUpdateWithMixedResults(t *testing.T) {
|
|||
if *tk.APIFailure != errorx.FailureEOFError {
|
||||
t.Fatal("invalid ApiFailure")
|
||||
}
|
||||
if tk.FailingGateways != nil {
|
||||
t.Fatal("invalid FailingGateways")
|
||||
}
|
||||
if tk.TransportStatus != nil {
|
||||
t.Fatal("invalid TransportStatus")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureCaCertFetch(t *testing.T) {
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
func TestInvalidCaCert(t *testing.T) {
|
||||
requestResponseMap := map[string]string{
|
||||
eipserviceurl: eipservice,
|
||||
providerurl: provider,
|
||||
geoserviceurl: geoservice,
|
||||
cacerturl: "invalid",
|
||||
openvpnurl1: "",
|
||||
openvpnurl2: "",
|
||||
obfs4url1: "",
|
||||
}
|
||||
measurer := riseupvpn.Measurer{
|
||||
Config: riseupvpn.Config{},
|
||||
Getter: generateMockGetter(requestResponseMap, map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: false,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// we're cancelling immediately so that the CA Cert fetch fails
|
||||
cancel()
|
||||
defer cancel()
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
|
@ -147,6 +331,26 @@ func TestFailureCaCertFetch(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.CACertStatus == true {
|
||||
t.Fatal("unexpected CaCertStatus")
|
||||
}
|
||||
if tk.APIStatus != "blocked" {
|
||||
t.Fatal("ApiStatus should be blocked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureCaCertFetch(t *testing.T) {
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: false,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.CACertStatus != false {
|
||||
t.Fatal("invalid CACertStatus ")
|
||||
|
@ -164,21 +368,15 @@ func TestFailureCaCertFetch(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailureEipServiceBlocked(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
selfcensor.Enable(`{"PoisonSystemDNS":{"api.black.riseup.net":["NXDOMAIN"]}}`)
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
callbacks := model.NewPrinterCallbacks(log.Log)
|
||||
err := measurer.Run(ctx, sess, measurement, callbacks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: false,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.CACertStatus != true {
|
||||
t.Fatal("invalid CACertStatus ")
|
||||
|
@ -202,21 +400,15 @@ func TestFailureEipServiceBlocked(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailureProviderUrlBlocked(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
selfcensor.Enable(`{"BlockedEndpoints":{"198.252.153.70:443":"REJECT"}}`)
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
callbacks := model.NewPrinterCallbacks(log.Log)
|
||||
err := measurer.Run(ctx, sess, measurement, callbacks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: false,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
|
||||
for _, entry := range tk.Requests {
|
||||
|
@ -240,21 +432,15 @@ func TestFailureProviderUrlBlocked(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailureGeoIpServiceBlocked(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
selfcensor.Enable(`{"BlockedEndpoints":{"198.252.153.107:9001":"REJECT"}}`)
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
callbacks := model.NewPrinterCallbacks(log.Log)
|
||||
err := measurer.Run(ctx, sess, measurement, callbacks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: false,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.CACertStatus != true {
|
||||
t.Fatal("invalid CACertStatus ")
|
||||
|
@ -277,138 +463,16 @@ func TestFailureGeoIpServiceBlocked(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFailureGateway(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
var testCases = [...]string{"openvpn", "obfs4"}
|
||||
eipService, err := fetchEipService()
|
||||
if err != nil {
|
||||
t.Log("Preconditions for the test are not met. Skipping due to: " + err.Error())
|
||||
t.SkipNow()
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("testing censored transport %s", tc), func(t *testing.T) {
|
||||
censoredGateway, err := selfCensorRandomGateway(eipService, tc)
|
||||
if err == nil {
|
||||
censorString := `{"BlockedEndpoints":{"` + censoredGateway.IP + `:` + censoredGateway.Port + `":"REJECT"}}`
|
||||
selfcensor.Enable(censorString)
|
||||
} else {
|
||||
t.Log("Preconditions for the test are not met. Skipping due to: " + err.Error())
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
// - run measurement
|
||||
runGatewayTest(t, censoredGateway)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type SelfCensoredGateway struct {
|
||||
IP string
|
||||
Port string
|
||||
}
|
||||
|
||||
func fetchEipService() (*riseupvpn.EipService, error) {
|
||||
// - fetch client cert and add to certpool
|
||||
caFetchClient := &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
|
||||
caCertResponse, err := caFetchClient.Get("https://black.riseup.net/ca.crt")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bodyString string
|
||||
|
||||
if caCertResponse.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("unexpected HTTP response code")
|
||||
}
|
||||
bodyBytes, err := ioutil.ReadAll(caCertResponse.Body)
|
||||
defer caCertResponse.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyString = string(bodyBytes)
|
||||
|
||||
certs := x509.NewCertPool()
|
||||
certs.AppendCertsFromPEM([]byte(bodyString))
|
||||
|
||||
// - fetch and parse eip-service.json
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: certs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
eipResponse, err := client.Get("https://api.black.riseup.net/3/config/eip-service.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eipResponse.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("Unexpected HTTP response code")
|
||||
}
|
||||
|
||||
bodyBytes, err = ioutil.ReadAll(eipResponse.Body)
|
||||
defer eipResponse.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyString = string(bodyBytes)
|
||||
|
||||
eipService, err := riseupvpn.DecodeEIP3(bodyString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return eipService, nil
|
||||
}
|
||||
|
||||
func selfCensorRandomGateway(eipService *riseupvpn.EipService, transportType string) (*SelfCensoredGateway, error) {
|
||||
|
||||
// - self censor random gateway
|
||||
gateways := eipService.Gateways
|
||||
if gateways == nil || len(gateways) == 0 {
|
||||
return nil, errors.New("No gateways found")
|
||||
}
|
||||
|
||||
var selfcensoredGateways []SelfCensoredGateway
|
||||
for _, gateway := range gateways {
|
||||
for _, transport := range gateway.Capabilities.Transport {
|
||||
if transport.Type == transportType {
|
||||
selfcensoredGateways = append(selfcensoredGateways, SelfCensoredGateway{IP: gateway.IPAddress, Port: transport.Ports[0]})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(selfcensoredGateways) == 0 {
|
||||
return nil, errors.New("transport " + transportType + " doesn't seem to be supported.")
|
||||
}
|
||||
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
min := 0
|
||||
max := len(selfcensoredGateways) - 1
|
||||
randomIndex := rnd.Intn(max-min+1) + min
|
||||
return &selfcensoredGateways[randomIndex], nil
|
||||
|
||||
}
|
||||
|
||||
func runGatewayTest(t *testing.T, censoredGateway *SelfCensoredGateway) {
|
||||
measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
callbacks := model.NewPrinterCallbacks(log.Log)
|
||||
err := measurer.Run(ctx, sess, measurement, callbacks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
func TestFailureGateway1(t *testing.T) {
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: false,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: true,
|
||||
}))
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.CACertStatus != true {
|
||||
t.Fatal("invalid CACertStatus ")
|
||||
|
@ -418,18 +482,125 @@ func runGatewayTest(t *testing.T, censoredGateway *SelfCensoredGateway) {
|
|||
t.Fatal("unexpected amount of failing gateways")
|
||||
}
|
||||
|
||||
entry := tk.FailingGateways[0]
|
||||
if entry.IP != censoredGateway.IP || fmt.Sprint(entry.Port) != censoredGateway.Port {
|
||||
t.Fatal("unexpected failed gateway configuration")
|
||||
gw := tk.FailingGateways[0]
|
||||
if gw.IP != "234.345.234.345" {
|
||||
t.Fatal("invalid failed gateway ip: " + fmt.Sprint(gw.IP))
|
||||
}
|
||||
if gw.Port != 443 {
|
||||
t.Fatal("invalid failed gateway port: " + fmt.Sprint(gw.Port))
|
||||
}
|
||||
if gw.TransportType != "openvpn" {
|
||||
t.Fatal("invalid failed transport type: " + fmt.Sprint(gw.TransportType))
|
||||
}
|
||||
|
||||
if tk.APIStatus == "blocked" {
|
||||
t.Fatal("invalid ApiStatus", tk.APIStatus)
|
||||
t.Fatal("invalid ApiStatus")
|
||||
}
|
||||
|
||||
if tk.APIFailure != nil {
|
||||
t.Fatal("ApiFailure should be null")
|
||||
}
|
||||
|
||||
if tk.TransportStatus == nil || tk.TransportStatus["openvpn"] == "blocked" {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
|
||||
if tk.TransportStatus == nil || tk.TransportStatus["obfs4"] == "blocked" {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureTransport(t *testing.T) {
|
||||
measurement := runDefaultMockTest(t, generateDefaultMockGetter(map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: false,
|
||||
openvpnurl2: false,
|
||||
obfs4url1: false,
|
||||
}))
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
|
||||
if tk.TransportStatus == nil || tk.TransportStatus["openvpn"] != "blocked" {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
|
||||
if tk.TransportStatus == nil || tk.TransportStatus["obfs4"] != "blocked" {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingTransport(t *testing.T) {
|
||||
eipService, err := riseupvpn.DecodeEIP3(eipservice)
|
||||
if err != nil {
|
||||
t.Fatal("Preconditions for the test are not met.")
|
||||
}
|
||||
|
||||
//remove obfs4 capability from 2. gateway so that our
|
||||
//mock provider supports only openvpn
|
||||
index := -1
|
||||
transports := eipService.Gateways[1].Capabilities.Transport
|
||||
for i, transport := range transports {
|
||||
if transport.Type == "obfs4" {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
t.Fatal("Preconditions for the test are not met. Default eipservice string should contain obfs4 transport.")
|
||||
}
|
||||
|
||||
transports[index] = transports[len(transports)-1]
|
||||
transports = transports[:len(transports)-1]
|
||||
eipService.Gateways[1].Capabilities.Transport = transports
|
||||
eipservicejson, err := json.Marshal(eipservice)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requestResponseMap := map[string]string{
|
||||
eipserviceurl: string(eipservicejson),
|
||||
providerurl: provider,
|
||||
geoserviceurl: geoservice,
|
||||
cacerturl: cacert,
|
||||
openvpnurl1: "",
|
||||
openvpnurl2: "",
|
||||
obfs4url1: "",
|
||||
}
|
||||
|
||||
measurer := riseupvpn.Measurer{
|
||||
Config: riseupvpn.Config{},
|
||||
Getter: generateMockGetter(requestResponseMap, map[string]bool{
|
||||
cacerturl: true,
|
||||
eipserviceurl: true,
|
||||
providerurl: true,
|
||||
geoserviceurl: true,
|
||||
openvpnurl1: true,
|
||||
openvpnurl2: true,
|
||||
obfs4url1: false,
|
||||
}),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
sess := &mockable.Session{MockableLogger: log.Log}
|
||||
measurement := new(model.Measurement)
|
||||
callbacks := model.NewPrinterCallbacks(log.Log)
|
||||
err = measurer.Run(ctx, sess, measurement, callbacks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tk := measurement.TestKeys.(*riseupvpn.TestKeys)
|
||||
if tk.TransportStatus == nil || tk.TransportStatus["openvpn"] != "blocked" {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
|
||||
if _, found := tk.TransportStatus["obfs"]; found {
|
||||
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSummaryKeysInvalidType(t *testing.T) {
|
||||
|
@ -450,21 +621,27 @@ func TestSummaryKeysWorksAsIntended(t *testing.T) {
|
|||
APIStatus: "blocked",
|
||||
CACertStatus: true,
|
||||
FailingGateways: nil,
|
||||
TransportStatus: nil,
|
||||
},
|
||||
sk: riseupvpn.SummaryKeys{
|
||||
APIBlocked: true,
|
||||
ValidCACert: true,
|
||||
IsAnomaly: true,
|
||||
APIBlocked: true,
|
||||
ValidCACert: true,
|
||||
IsAnomaly: true,
|
||||
TransportStatus: nil,
|
||||
FailingGateways: 0,
|
||||
},
|
||||
}, {
|
||||
tk: riseupvpn.TestKeys{
|
||||
APIStatus: "ok",
|
||||
CACertStatus: false,
|
||||
FailingGateways: nil,
|
||||
TransportStatus: nil,
|
||||
},
|
||||
sk: riseupvpn.SummaryKeys{
|
||||
ValidCACert: false,
|
||||
IsAnomaly: true,
|
||||
ValidCACert: false,
|
||||
IsAnomaly: true,
|
||||
FailingGateways: 0,
|
||||
TransportStatus: nil,
|
||||
},
|
||||
}, {
|
||||
tk: riseupvpn.TestKeys{
|
||||
|
@ -475,13 +652,39 @@ func TestSummaryKeysWorksAsIntended(t *testing.T) {
|
|||
Port: 443,
|
||||
TransportType: "obfs4",
|
||||
}},
|
||||
TransportStatus: map[string]string{
|
||||
"obfs4": "blocked",
|
||||
"openvpn": "ok",
|
||||
},
|
||||
},
|
||||
sk: riseupvpn.SummaryKeys{
|
||||
FailingGateways: 1,
|
||||
IsAnomaly: true,
|
||||
ValidCACert: true,
|
||||
TransportStatus: map[string]string{
|
||||
"obfs4": "blocked",
|
||||
"openvpn": "ok",
|
||||
},
|
||||
},
|
||||
}}
|
||||
}, {
|
||||
tk: riseupvpn.TestKeys{
|
||||
APIStatus: "ok",
|
||||
CACertStatus: true,
|
||||
FailingGateways: nil,
|
||||
TransportStatus: map[string]string{
|
||||
"openvpn": "ok",
|
||||
},
|
||||
},
|
||||
sk: riseupvpn.SummaryKeys{
|
||||
ValidCACert: true,
|
||||
IsAnomaly: false,
|
||||
FailingGateways: 0,
|
||||
TransportStatus: map[string]string{
|
||||
"openvpn": "ok",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for idx, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
||||
m := &riseupvpn.Measurer{}
|
||||
|
@ -498,3 +701,95 @@ func TestSummaryKeysWorksAsIntended(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateMockGetter(requestResponse map[string]string, responseStatus map[string]bool) urlgetter.MultiGetter {
|
||||
return func(ctx context.Context, g urlgetter.Getter) (urlgetter.TestKeys, error) {
|
||||
url := g.Target
|
||||
responseBody, foundRequest := requestResponse[url]
|
||||
isSuccessStatus, foundStatus := responseStatus[url]
|
||||
if !foundRequest || !foundStatus {
|
||||
return urlgetter.DefaultMultiGetter(ctx, g)
|
||||
}
|
||||
|
||||
var failure *string
|
||||
var failedOperation *string
|
||||
isBlocked := !isSuccessStatus
|
||||
var responseStatus int64 = 200
|
||||
|
||||
if isBlocked {
|
||||
responseBody = ""
|
||||
eofError := io.EOF.Error()
|
||||
failure = &eofError
|
||||
connectOperation := errorx.ConnectOperation
|
||||
failedOperation = &connectOperation
|
||||
responseStatus = 0
|
||||
}
|
||||
|
||||
tcpConnect := archival.TCPConnectEntry{
|
||||
// use some dummy IP/Port combination for URLs, we don't do DNS resolution
|
||||
IP: "123.456.234.123",
|
||||
Port: 443,
|
||||
Status: archival.TCPConnectStatus{
|
||||
Success: isSuccessStatus,
|
||||
Blocked: &isBlocked,
|
||||
Failure: failure,
|
||||
},
|
||||
}
|
||||
if strings.Contains(url, "tcpconnect://") {
|
||||
ipPort := strings.Split(strings.Split(url, "//")[1], ":")
|
||||
port, err := strconv.ParseInt(ipPort[1], 10, 32)
|
||||
if err == nil {
|
||||
tcpConnect.IP = ipPort[0]
|
||||
tcpConnect.Port = int(port)
|
||||
}
|
||||
}
|
||||
|
||||
tk := urlgetter.TestKeys{
|
||||
Failure: failure,
|
||||
FailedOperation: failedOperation,
|
||||
HTTPResponseStatus: responseStatus,
|
||||
HTTPResponseBody: responseBody,
|
||||
Requests: []archival.RequestEntry{{
|
||||
Failure: failure,
|
||||
Request: archival.HTTPRequest{
|
||||
URL: url,
|
||||
Body: archival.MaybeBinaryValue{},
|
||||
BodyIsTruncated: false,
|
||||
},
|
||||
Response: archival.HTTPResponse{
|
||||
Body: archival.HTTPBody{
|
||||
Value: responseBody,
|
||||
},
|
||||
BodyIsTruncated: false,
|
||||
}},
|
||||
},
|
||||
TCPConnect: []archival.TCPConnectEntry{tcpConnect},
|
||||
}
|
||||
return tk, nil
|
||||
}
|
||||
}
|
||||
func generateDefaultMockGetter(responseStatuses map[string]bool) urlgetter.MultiGetter {
|
||||
return generateMockGetter(RequestResponse, responseStatuses)
|
||||
}
|
||||
|
||||
func runDefaultMockTest(t *testing.T, multiGetter urlgetter.MultiGetter) *model.Measurement {
|
||||
measurer := riseupvpn.Measurer{
|
||||
Config: riseupvpn.Config{},
|
||||
Getter: multiGetter,
|
||||
}
|
||||
|
||||
measurement := new(model.Measurement)
|
||||
err := measurer.Run(
|
||||
context.Background(),
|
||||
&mockable.Session{
|
||||
MockableLogger: log.Log,
|
||||
},
|
||||
measurement,
|
||||
model.NewPrinterCallbacks(log.Log),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return measurement
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ func (tk *TestKeys) run(
|
|||
tk.NetworkEvents, archival.NewNetworkEventsList(begin, events)...,
|
||||
)
|
||||
tk.Queries = append(
|
||||
tk.Queries, archival.NewDNSQueriesList(begin, events, sess.ASNDatabasePath())...,
|
||||
tk.Queries, archival.NewDNSQueriesList(begin, events)...,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ func SNISplitter(input []byte, sni []byte) (output [][]byte) {
|
|||
}
|
||||
if len(buf) > 0 {
|
||||
output = append(output, buf)
|
||||
buf = nil
|
||||
}
|
||||
output = append(output, input[idx+len(sni):])
|
||||
return
|
||||
|
|
|
@ -58,10 +58,7 @@ func (g Getter) Get(ctx context.Context) (TestKeys, error) {
|
|||
tk.FailedOperation = archival.NewFailedOperation(err)
|
||||
tk.Failure = archival.NewFailure(err)
|
||||
events := saver.Read()
|
||||
tk.Queries = append(
|
||||
tk.Queries, archival.NewDNSQueriesList(
|
||||
g.Begin, events, g.Session.ASNDatabasePath())...,
|
||||
)
|
||||
tk.Queries = append(tk.Queries, archival.NewDNSQueriesList(g.Begin, events)...)
|
||||
tk.NetworkEvents = append(
|
||||
tk.NetworkEvents, archival.NewNetworkEventsList(g.Begin, events)...,
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package webconnectivity
|
|||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
|
||||
// ConnectsConfig contains the config for Connects
|
||||
type ConnectsConfig struct {
|
||||
Begin time.Time
|
||||
Session model.ExperimentSession
|
||||
TargetURL *url.URL
|
||||
URLGetterURLs []string
|
||||
|
@ -28,7 +30,7 @@ type ConnectsResult struct {
|
|||
// check whether the resolved endpoints are reachable.
|
||||
func Connects(ctx context.Context, config ConnectsConfig) (out ConnectsResult) {
|
||||
out.AllKeys = []urlgetter.TestKeys{}
|
||||
multi := urlgetter.Multi{Session: config.Session}
|
||||
multi := urlgetter.Multi{Begin: config.Begin, Session: config.Session}
|
||||
inputs := []urlgetter.MultiInput{}
|
||||
for _, url := range config.URLGetterURLs {
|
||||
inputs = append(inputs, urlgetter.MultiInput{
|
||||
|
|
|
@ -78,7 +78,7 @@ func (dns *ControlDNSResult) FillASNs(sess model.ExperimentSession) {
|
|||
for _, ip := range dns.Addrs {
|
||||
// TODO(bassosimone): this would be more efficient if we'd open just
|
||||
// once the database and then reuse it for every address.
|
||||
asn, _, _ := geolocate.LookupASN(sess.ASNDatabasePath(), ip)
|
||||
asn, _, _ := geolocate.LookupASN(ip)
|
||||
dns.ASNs = append(dns.ASNs, int64(asn))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,6 @@ func TestFillASNsEmpty(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFillASNsNoDatabase(t *testing.T) {
|
||||
dns := new(webconnectivity.ControlDNSResult)
|
||||
dns.Addrs = []string{"8.8.8.8", "1.1.1.1"}
|
||||
dns.FillASNs(new(mockable.Session))
|
||||
if diff := cmp.Diff(dns.ASNs, []int64{0, 0}); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillASNsSuccess(t *testing.T) {
|
||||
sess := newsession(t, false)
|
||||
dns := new(webconnectivity.ControlDNSResult)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
|
||||
// DNSLookupConfig contains settings for the DNS lookup.
|
||||
type DNSLookupConfig struct {
|
||||
Begin time.Time
|
||||
Session model.ExperimentSession
|
||||
URL *url.URL
|
||||
}
|
||||
|
@ -27,7 +29,8 @@ type DNSLookupResult struct {
|
|||
func DNSLookup(ctx context.Context, config DNSLookupConfig) (out DNSLookupResult) {
|
||||
target := fmt.Sprintf("dnslookup://%s", config.URL.Hostname())
|
||||
config.Session.Logger().Infof("%s...", target)
|
||||
result, err := urlgetter.Getter{Session: config.Session, Target: target}.Get(ctx)
|
||||
result, err := urlgetter.Getter{
|
||||
Begin: config.Begin, Session: config.Session, Target: target}.Get(ctx)
|
||||
out.Addrs = make(map[string]int64)
|
||||
for _, query := range result.Queries {
|
||||
for _, answer := range query.Answers {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
// HTTPGetConfig contains the config for HTTPGet
|
||||
type HTTPGetConfig struct {
|
||||
Addresses []string
|
||||
Begin time.Time
|
||||
Session model.ExperimentSession
|
||||
TargetURL *url.URL
|
||||
}
|
||||
|
@ -54,6 +56,7 @@ func HTTPGet(ctx context.Context, config HTTPGetConfig) (out HTTPGetResult) {
|
|||
config.Session.Logger().Infof("GET %s...", target)
|
||||
domain := config.TargetURL.Hostname()
|
||||
result, err := urlgetter.Getter{
|
||||
Begin: config.Begin,
|
||||
Config: urlgetter.Config{
|
||||
DNSCache: HTTPGetMakeDNSCache(domain, addresses),
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
const (
|
||||
testName = "web_connectivity"
|
||||
testVersion = "0.3.0"
|
||||
testVersion = "0.4.0"
|
||||
)
|
||||
|
||||
// Config contains the experiment config.
|
||||
|
@ -32,6 +32,15 @@ type TestKeys struct {
|
|||
Retries *int64 `json:"retries"` // unused
|
||||
SOCKSProxy *string `json:"socksproxy"` // unused
|
||||
|
||||
// For now mostly TCP/TLS "connect" experiment but we are
|
||||
// considering adding more events. An open question is
|
||||
// currently how to properly tag these events so that it
|
||||
// is rather obvious where they come from.
|
||||
//
|
||||
// See https://github.com/ooni/probe/issues/1413.
|
||||
NetworkEvents []archival.NetworkEvent `json:"network_events"`
|
||||
TLSHandshakes []archival.TLSHandshake `json:"tls_handshakes"`
|
||||
|
||||
// DNS experiment
|
||||
Queries []archival.DNSQueryEntry `json:"queries"`
|
||||
DNSExperimentFailure *string `json:"dns_experiment_failure"`
|
||||
|
@ -42,7 +51,7 @@ type TestKeys struct {
|
|||
ControlRequest ControlRequest `json:"-"`
|
||||
Control ControlResponse `json:"control"`
|
||||
|
||||
// TCP connect experiment
|
||||
// TCP/TLS "connect" experiment
|
||||
TCPConnect []archival.TCPConnectEntry `json:"tcp_connect"`
|
||||
TCPConnectSuccesses int `json:"-"`
|
||||
TCPConnectAttempts int `json:"-"`
|
||||
|
@ -90,6 +99,19 @@ var (
|
|||
ErrUnsupportedInput = errors.New("unsupported input scheme")
|
||||
)
|
||||
|
||||
// Tags describing the section of this experiment in which
|
||||
// the data has been collected.
|
||||
const (
|
||||
// DNSExperimentTag is a tag indicating the DNS experiment.
|
||||
DNSExperimentTag = "dns_experiment"
|
||||
|
||||
// TCPTLSExperimentTag is a tag indicating the connect experiment.
|
||||
TCPTLSExperimentTag = "tcptls_experiment"
|
||||
|
||||
// HTTPExperimentTag is a tag indicating the HTTP experiment.
|
||||
HTTPExperimentTag = "http_experiment"
|
||||
)
|
||||
|
||||
// Run implements ExperimentMeasurer.Run.
|
||||
func (m Measurer) Run(
|
||||
ctx context.Context,
|
||||
|
@ -129,7 +151,9 @@ func (m Measurer) Run(
|
|||
"backend": testhelper,
|
||||
}
|
||||
// 2. perform the DNS lookup step
|
||||
dnsResult := DNSLookup(ctx, DNSLookupConfig{Session: sess, URL: URL})
|
||||
dnsResult := DNSLookup(ctx, DNSLookupConfig{
|
||||
Begin: measurement.MeasurementStartTimeSaved,
|
||||
Session: sess, URL: URL})
|
||||
tk.Queries = append(tk.Queries, dnsResult.TestKeys.Queries...)
|
||||
tk.DNSExperimentFailure = dnsResult.Failure
|
||||
epnts := NewEndpoints(URL, dnsResult.Addresses())
|
||||
|
@ -152,7 +176,13 @@ func (m Measurer) Run(
|
|||
sess.Logger().Infof("DNS analysis result: %+v", internal.StringPointerToString(
|
||||
tk.DNSAnalysisResult.DNSConsistency))
|
||||
// 5. perform TCP/TLS connects
|
||||
//
|
||||
// TODO(bassosimone): here we should also follow the IP addresses
|
||||
// returned by the control experiment.
|
||||
//
|
||||
// See https://github.com/ooni/probe/issues/1414
|
||||
connectsResult := Connects(ctx, ConnectsConfig{
|
||||
Begin: measurement.MeasurementStartTimeSaved,
|
||||
Session: sess,
|
||||
TargetURL: URL,
|
||||
URLGetterURLs: epnts.URLs(),
|
||||
|
@ -164,12 +194,21 @@ func (m Measurer) Run(
|
|||
// sad that we're storing analysis result inside the measurement
|
||||
tk.TCPConnect = append(tk.TCPConnect, ComputeTCPBlocking(
|
||||
tcpkeys.TCPConnect, tk.Control.TCPConnect)...)
|
||||
for _, ev := range tcpkeys.NetworkEvents {
|
||||
ev.Tags = []string{TCPTLSExperimentTag}
|
||||
tk.NetworkEvents = append(tk.NetworkEvents, ev)
|
||||
}
|
||||
for _, ev := range tcpkeys.TLSHandshakes {
|
||||
ev.Tags = []string{TCPTLSExperimentTag}
|
||||
tk.TLSHandshakes = append(tk.TLSHandshakes, ev)
|
||||
}
|
||||
}
|
||||
tk.TCPConnectAttempts = connectsResult.Total
|
||||
tk.TCPConnectSuccesses = connectsResult.Successes
|
||||
// 6. perform HTTP/HTTPS measurement
|
||||
httpResult := HTTPGet(ctx, HTTPGetConfig{
|
||||
Addresses: dnsResult.Addresses(),
|
||||
Begin: measurement.MeasurementStartTimeSaved,
|
||||
Session: sess,
|
||||
TargetURL: URL,
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestNewExperimentMeasurer(t *testing.T) {
|
|||
if measurer.ExperimentName() != "web_connectivity" {
|
||||
t.Fatal("unexpected name")
|
||||
}
|
||||
if measurer.ExperimentVersion() != "0.3.0" {
|
||||
if measurer.ExperimentVersion() != "0.4.0" {
|
||||
t.Fatal("unexpected version")
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +202,6 @@ func TestMeasureWithNoAvailableTestHelpers(t *testing.T) {
|
|||
|
||||
func newsession(t *testing.T, lookupBackends bool) model.ExperimentSession {
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "../../testdata",
|
||||
AvailableProbeServices: []model.Service{{
|
||||
Address: "https://ams-pg-test.ooni.org",
|
||||
Type: "https",
|
||||
|
|
|
@ -142,7 +142,7 @@ func TestNeedsInput(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if builder.InputPolicy() != InputOrQueryTestLists {
|
||||
if builder.InputPolicy() != InputOrQueryBackend {
|
||||
t.Fatal("web_connectivity certainly needs input")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@ import (
|
|||
type InputPolicy string
|
||||
|
||||
const (
|
||||
// InputOrQueryTestLists indicates that the experiment requires
|
||||
// InputOrQueryBackend indicates that the experiment requires
|
||||
// external input to run and that this kind of input is URLs
|
||||
// from the citizenlab/test-lists repository. If this input
|
||||
// not provided to the experiment, then the code that runs the
|
||||
// experiment is supposed to fetch from URLs from OONI's backends.
|
||||
InputOrQueryTestLists = InputPolicy("or_query_test_lists")
|
||||
InputOrQueryBackend = InputPolicy("or_query_backend")
|
||||
|
||||
// InputStrictlyRequired indicates that the experiment
|
||||
// requires input and we currently don't have an API for
|
||||
|
@ -193,7 +193,7 @@ func canonicalizeExperimentName(name string) string {
|
|||
}
|
||||
|
||||
func newExperimentBuilder(session *Session, name string) (*ExperimentBuilder, error) {
|
||||
factory, _ := experimentsByName[canonicalizeExperimentName(name)]
|
||||
factory := experimentsByName[canonicalizeExperimentName(name)]
|
||||
if factory == nil {
|
||||
return nil, fmt.Errorf("no such experiment: %s", name)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package geolocate
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
|
@ -43,12 +42,6 @@ var (
|
|||
DefaultResolverASNString = fmt.Sprintf("AS%d", DefaultResolverASN)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMissingResourcesManager indicates that no resources
|
||||
// manager has been configured inside of Config.
|
||||
ErrMissingResourcesManager = errors.New("geolocate: ResourcesManager is nil")
|
||||
)
|
||||
|
||||
// Logger is the definition of Logger used by this package.
|
||||
type Logger interface {
|
||||
Debug(msg string)
|
||||
|
@ -96,30 +89,17 @@ type probeIPLookupper interface {
|
|||
}
|
||||
|
||||
type asnLookupper interface {
|
||||
LookupASN(path string, ip string) (asn uint, network string, err error)
|
||||
LookupASN(ip string) (asn uint, network string, err error)
|
||||
}
|
||||
|
||||
type countryLookupper interface {
|
||||
LookupCC(path string, ip string) (cc string, err error)
|
||||
LookupCC(ip string) (cc string, err error)
|
||||
}
|
||||
|
||||
type resolverIPLookupper interface {
|
||||
LookupResolverIP(ctx context.Context) (addr string, err error)
|
||||
}
|
||||
|
||||
// ResourcesManager manages the required resources.
|
||||
type ResourcesManager interface {
|
||||
// ASNDatabasePath returns the path of the ASN database.
|
||||
ASNDatabasePath() string
|
||||
|
||||
// CountryDatabasePath returns the path of the country database.
|
||||
CountryDatabasePath() string
|
||||
|
||||
// MaybeUpdateResources ensures that the required resources
|
||||
// have been downloaded and are current.
|
||||
MaybeUpdateResources(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Resolver is a DNS resolver.
|
||||
type Resolver interface {
|
||||
LookupHost(ctx context.Context, domain string) ([]string, error)
|
||||
|
@ -142,10 +122,6 @@ type Config struct {
|
|||
// use a logger that discards all messages.
|
||||
Logger Logger
|
||||
|
||||
// ResourcesManager is the mandatory resources manager. If not
|
||||
// set, we will not be able to perform any lookup.
|
||||
ResourcesManager ResourcesManager
|
||||
|
||||
// UserAgent is the user agent to use. If not set, then
|
||||
// we will use a default user agent.
|
||||
UserAgent string
|
||||
|
@ -162,9 +138,6 @@ func NewTask(config Config) (*Task, error) {
|
|||
if config.Logger == nil {
|
||||
config.Logger = model.DiscardLogger
|
||||
}
|
||||
if config.ResourcesManager == nil {
|
||||
return nil, ErrMissingResourcesManager
|
||||
}
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = fmt.Sprintf("ooniprobe-engine/%s", version.Version)
|
||||
}
|
||||
|
@ -183,7 +156,6 @@ func NewTask(config Config) (*Task, error) {
|
|||
probeASNLookupper: mmdbLookupper{},
|
||||
resolverASNLookupper: mmdbLookupper{},
|
||||
resolverIPLookupper: resolverLookupClient{},
|
||||
resourcesManager: config.ResourcesManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -196,7 +168,6 @@ type Task struct {
|
|||
probeASNLookupper asnLookupper
|
||||
resolverASNLookupper asnLookupper
|
||||
resolverIPLookupper resolverIPLookupper
|
||||
resourcesManager ResourcesManager
|
||||
}
|
||||
|
||||
// Run runs the task.
|
||||
|
@ -211,23 +182,18 @@ func (op Task) Run(ctx context.Context) (*Results, error) {
|
|||
ResolverIP: DefaultResolverIP,
|
||||
ResolverNetworkName: DefaultResolverNetworkName,
|
||||
}
|
||||
if err := op.resourcesManager.MaybeUpdateResources(ctx); err != nil {
|
||||
return out, fmt.Errorf("MaybeUpdateResource failed: %w", err)
|
||||
}
|
||||
ip, err := op.probeIPLookupper.LookupProbeIP(ctx)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("lookupProbeIP failed: %w", err)
|
||||
}
|
||||
out.ProbeIP = ip
|
||||
asn, networkName, err := op.probeASNLookupper.LookupASN(
|
||||
op.resourcesManager.ASNDatabasePath(), out.ProbeIP)
|
||||
asn, networkName, err := op.probeASNLookupper.LookupASN(out.ProbeIP)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("lookupASN failed: %w", err)
|
||||
}
|
||||
out.ASN = asn
|
||||
out.NetworkName = networkName
|
||||
cc, err := op.countryLookupper.LookupCC(
|
||||
op.resourcesManager.CountryDatabasePath(), out.ProbeIP)
|
||||
cc, err := op.countryLookupper.LookupCC(out.ProbeIP)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("lookupProbeCC failed: %w", err)
|
||||
}
|
||||
|
@ -244,7 +210,7 @@ func (op Task) Run(ctx context.Context) (*Results, error) {
|
|||
}
|
||||
out.ResolverIP = resolverIP
|
||||
resolverASN, resolverNetworkName, err := op.resolverASNLookupper.LookupASN(
|
||||
op.resourcesManager.ASNDatabasePath(), out.ResolverIP,
|
||||
out.ResolverIP,
|
||||
)
|
||||
if err != nil {
|
||||
return out, nil
|
||||
|
|
|
@ -6,57 +6,6 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
type taskResourcesManager struct {
|
||||
asnDatabasePath string
|
||||
countryDatabasePath string
|
||||
err error
|
||||
}
|
||||
|
||||
func (c taskResourcesManager) ASNDatabasePath() string {
|
||||
return c.asnDatabasePath
|
||||
}
|
||||
|
||||
func (c taskResourcesManager) CountryDatabasePath() string {
|
||||
return c.countryDatabasePath
|
||||
}
|
||||
|
||||
func (c taskResourcesManager) MaybeUpdateResources(ctx context.Context) error {
|
||||
return c.err
|
||||
}
|
||||
|
||||
func TestLocationLookupCannotUpdateResources(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{err: expected},
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := op.Run(ctx)
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out.ASN != DefaultProbeASN {
|
||||
t.Fatal("invalid ASN value")
|
||||
}
|
||||
if out.CountryCode != DefaultProbeCC {
|
||||
t.Fatal("invalid CountryCode value")
|
||||
}
|
||||
if out.NetworkName != DefaultProbeNetworkName {
|
||||
t.Fatal("invalid NetworkName value")
|
||||
}
|
||||
if out.ProbeIP != DefaultProbeIP {
|
||||
t.Fatal("invalid ProbeIP value")
|
||||
}
|
||||
if out.ResolverASN != DefaultResolverASN {
|
||||
t.Fatal("invalid ResolverASN value")
|
||||
}
|
||||
if out.ResolverIP != DefaultResolverIP {
|
||||
t.Fatal("invalid ResolverIP value")
|
||||
}
|
||||
if out.ResolverNetworkName != DefaultResolverNetworkName {
|
||||
t.Fatal("invalid ResolverNetworkName value")
|
||||
}
|
||||
}
|
||||
|
||||
type taskProbeIPLookupper struct {
|
||||
ip string
|
||||
err error
|
||||
|
@ -69,7 +18,6 @@ func (c taskProbeIPLookupper) LookupProbeIP(ctx context.Context) (string, error)
|
|||
func TestLocationLookupCannotLookupProbeIP(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{err: expected},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -106,14 +54,13 @@ type taskASNLookupper struct {
|
|||
name string
|
||||
}
|
||||
|
||||
func (c taskASNLookupper) LookupASN(path string, ip string) (uint, string, error) {
|
||||
func (c taskASNLookupper) LookupASN(ip string) (uint, string, error) {
|
||||
return c.asn, c.name, c.err
|
||||
}
|
||||
|
||||
func TestLocationLookupCannotLookupProbeASN(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{err: expected},
|
||||
}
|
||||
|
@ -150,14 +97,13 @@ type taskCCLookupper struct {
|
|||
cc string
|
||||
}
|
||||
|
||||
func (c taskCCLookupper) LookupCC(path string, ip string) (string, error) {
|
||||
func (c taskCCLookupper) LookupCC(ip string) (string, error) {
|
||||
return c.cc, c.err
|
||||
}
|
||||
|
||||
func TestLocationLookupCannotLookupProbeCC(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{asn: 1234, name: "1234.com"},
|
||||
countryLookupper: taskCCLookupper{cc: "US", err: expected},
|
||||
|
@ -202,7 +148,6 @@ func (c taskResolverIPLookupper) LookupResolverIP(ctx context.Context) (string,
|
|||
func TestLocationLookupCannotLookupResolverIP(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{asn: 1234, name: "1234.com"},
|
||||
countryLookupper: taskCCLookupper{cc: "IT"},
|
||||
|
@ -243,7 +188,6 @@ func TestLocationLookupCannotLookupResolverIP(t *testing.T) {
|
|||
func TestLocationLookupCannotLookupResolverNetworkName(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{asn: 1234, name: "1234.com"},
|
||||
countryLookupper: taskCCLookupper{cc: "IT"},
|
||||
|
@ -284,7 +228,6 @@ func TestLocationLookupCannotLookupResolverNetworkName(t *testing.T) {
|
|||
|
||||
func TestLocationLookupSuccessWithResolverLookup(t *testing.T) {
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{asn: 1234, name: "1234.com"},
|
||||
countryLookupper: taskCCLookupper{cc: "IT"},
|
||||
|
@ -325,7 +268,6 @@ func TestLocationLookupSuccessWithResolverLookup(t *testing.T) {
|
|||
|
||||
func TestLocationLookupSuccessWithoutResolverLookup(t *testing.T) {
|
||||
op := Task{
|
||||
resourcesManager: taskResourcesManager{},
|
||||
probeIPLookupper: taskProbeIPLookupper{ip: "1.2.3.4"},
|
||||
probeASNLookupper: taskASNLookupper{asn: 1234, name: "1234.com"},
|
||||
countryLookupper: taskCCLookupper{cc: "IT"},
|
||||
|
@ -364,13 +306,8 @@ func TestLocationLookupSuccessWithoutResolverLookup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSmoke(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
config := Config{
|
||||
EnableResolverLookup: true,
|
||||
ResourcesManager: taskResourcesManager{
|
||||
asnDatabasePath: asnDBPath,
|
||||
countryDatabasePath: countryDBPath,
|
||||
},
|
||||
}
|
||||
task := Must(NewTask(config))
|
||||
result, err := task.Run(context.Background())
|
||||
|
@ -384,16 +321,6 @@ func TestSmoke(t *testing.T) {
|
|||
// value is okay for all codepaths.
|
||||
}
|
||||
|
||||
func TestNewTaskWithNoResourcesManager(t *testing.T) {
|
||||
task, err := NewTask(Config{})
|
||||
if !errors.Is(err, ErrMissingResourcesManager) {
|
||||
t.Fatal("not the error we expected")
|
||||
}
|
||||
if task != nil {
|
||||
t.Fatal("expected nil task here")
|
||||
}
|
||||
}
|
||||
|
||||
func TestASNStringWorks(t *testing.T) {
|
||||
r := Results{ASN: 1234}
|
||||
if r.ASNString() != "AS1234" {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/apex/log"
|
||||
|
@ -11,6 +12,11 @@ import (
|
|||
)
|
||||
|
||||
func TestIPLookupWorksUsingIPConfig(t *testing.T) {
|
||||
if os.Getenv("CI") == "true" {
|
||||
// See https://github.com/ooni/probe-cli/pull/259/checks?check_run_id=2166066881#step:5:123
|
||||
// as well as https://github.com/ooni/probe/issues/1418.
|
||||
t.Skip("This test does not work with GitHub Actions")
|
||||
}
|
||||
ip, err := ipConfigIPLookup(
|
||||
context.Background(),
|
||||
http.DefaultClient,
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/internal/multierror"
|
||||
|
@ -61,8 +60,6 @@ var (
|
|||
fn: ubuntuIPLookup,
|
||||
},
|
||||
}
|
||||
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
type ipLookupClient struct {
|
||||
|
|
|
@ -3,14 +3,15 @@ package geolocate
|
|||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ooni/probe-assets/assets"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
type mmdbLookupper struct{}
|
||||
|
||||
func (mmdbLookupper) LookupASN(path, ip string) (asn uint, org string, err error) {
|
||||
func (mmdbLookupper) LookupASN(ip string) (asn uint, org string, err error) {
|
||||
asn, org = DefaultProbeASN, DefaultProbeNetworkName
|
||||
db, err := geoip2.Open(path)
|
||||
db, err := geoip2.FromBytes(assets.ASNDatabaseData())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -28,13 +29,13 @@ func (mmdbLookupper) LookupASN(path, ip string) (asn uint, org string, err error
|
|||
|
||||
// LookupASN returns the ASN and the organization associated with the
|
||||
// given ip using the ASN database at path.
|
||||
func LookupASN(path, ip string) (asn uint, org string, err error) {
|
||||
return (mmdbLookupper{}).LookupASN(path, ip)
|
||||
func LookupASN(ip string) (asn uint, org string, err error) {
|
||||
return (mmdbLookupper{}).LookupASN(ip)
|
||||
}
|
||||
|
||||
func (mmdbLookupper) LookupCC(path, ip string) (cc string, err error) {
|
||||
func (mmdbLookupper) LookupCC(ip string) (cc string, err error) {
|
||||
cc = DefaultProbeCC
|
||||
db, err := geoip2.Open(path)
|
||||
db, err := geoip2.FromBytes(assets.CountryDatabaseData())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,27 +1,11 @@
|
|||
package geolocate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
import "testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/resourcesmanager"
|
||||
)
|
||||
|
||||
const (
|
||||
asnDBPath = "../testdata/asn.mmdb"
|
||||
countryDBPath = "../testdata/country.mmdb"
|
||||
ipAddr = "35.204.49.125"
|
||||
)
|
||||
|
||||
func maybeFetchResources(t *testing.T) {
|
||||
c := &resourcesmanager.CopyWorker{DestDir: "../testdata/"}
|
||||
if err := c.Ensure(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
const ipAddr = "35.204.49.125"
|
||||
|
||||
func TestLookupASN(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
asn, org, err := LookupASN(asnDBPath, ipAddr)
|
||||
asn, org, err := LookupASN(ipAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -33,23 +17,8 @@ func TestLookupASN(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLookupASNInvalidFile(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
asn, org, err := LookupASN("/nonexistent", ipAddr)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if asn != DefaultProbeASN {
|
||||
t.Fatal("expected a zero ASN")
|
||||
}
|
||||
if org != DefaultProbeNetworkName {
|
||||
t.Fatal("expected an empty org")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupASNInvalidIP(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
asn, org, err := LookupASN(asnDBPath, "xxx")
|
||||
asn, org, err := LookupASN("xxx")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
|
@ -62,8 +31,7 @@ func TestLookupASNInvalidIP(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLookupCC(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
cc, err := (mmdbLookupper{}).LookupCC(countryDBPath, ipAddr)
|
||||
cc, err := (mmdbLookupper{}).LookupCC(ipAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -72,20 +40,8 @@ func TestLookupCC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLookupCCInvalidFile(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
cc, err := (mmdbLookupper{}).LookupCC("/nonexistent", ipAddr)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
if cc != DefaultProbeCC {
|
||||
t.Fatal("expected an empty cc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupCCInvalidIP(t *testing.T) {
|
||||
maybeFetchResources(t)
|
||||
cc, err := (mmdbLookupper{}).LookupCC(asnDBPath, "xxx")
|
||||
cc, err := (mmdbLookupper{}).LookupCC("xxx")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package httpheader
|
|||
|
||||
// UserAgent returns the User-Agent header used for measuring.
|
||||
func UserAgent() string {
|
||||
// 8.0% as of Mar 3, 2021 according to https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
|
||||
const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"
|
||||
// 11.4% as of Mar 31, 2021 according to https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
|
||||
const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36"
|
||||
return ua
|
||||
}
|
||||
|
||||
|
|
|
@ -5,28 +5,33 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/internal/fsx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
// The following errors are returned by the InputLoader.
|
||||
// These errors are returned by the InputLoader.
|
||||
var (
|
||||
ErrNoInputExpected = errors.New("we did not expect any input")
|
||||
ErrInputRequired = errors.New("no input provided")
|
||||
ErrNoURLsReturned = errors.New("no URLs returned")
|
||||
ErrDetectedEmptyFile = errors.New("file did not contain any input")
|
||||
ErrInputRequired = errors.New("no input provided")
|
||||
ErrNoInputExpected = errors.New("we did not expect any input")
|
||||
)
|
||||
|
||||
// InputLoaderSession is the session according to an InputLoader.
|
||||
// InputLoaderSession is the session according to an InputLoader. We
|
||||
// introduce this abstraction because it helps us with testing.
|
||||
type InputLoaderSession interface {
|
||||
MaybeLookupLocationContext(ctx context.Context) error
|
||||
NewOrchestraClient(ctx context.Context) (model.ExperimentOrchestraClient, error)
|
||||
ProbeCC() string
|
||||
CheckIn(ctx context.Context,
|
||||
config *model.CheckInConfig) (*model.CheckInInfo, error)
|
||||
}
|
||||
|
||||
// InputLoader loads input according to the specified policy
|
||||
// from the specified sources and OONI services. The behaviour
|
||||
// depends on the input policy as described below.
|
||||
// either from command line and input files or from OONI services. The
|
||||
// behaviour depends on the input policy as described below.
|
||||
//
|
||||
// You MUST NOT change any public field of this structure when
|
||||
// in use, because that MAY lead to data races.
|
||||
//
|
||||
// InputNone
|
||||
//
|
||||
|
@ -40,74 +45,52 @@ type InputLoaderSession interface {
|
|||
// input, we return it. Otherwise we return a single, empty entry
|
||||
// that causes experiments that don't require input to run once.
|
||||
//
|
||||
// InputOrQueryTestLists
|
||||
// InputOrQueryBackend
|
||||
//
|
||||
// We gather input from StaticInput and SourceFiles. If there is
|
||||
// input, we return it. Otherwise, we use OONI's probe services
|
||||
// to gather input using the test lists API.
|
||||
// to gather input using the best API for the task.
|
||||
//
|
||||
// InputStrictlyRequired
|
||||
//
|
||||
// Like InputOrQueryTestLists but, if there is no input, it's an
|
||||
// user error and we just abort running the experiment.
|
||||
type InputLoader interface {
|
||||
// Load attempts to load input using the specified input loader. We will
|
||||
// return a list of URLs because this is the only input we support.
|
||||
Load(ctx context.Context) ([]model.URLInfo, error)
|
||||
}
|
||||
// We gather input from StaticInput and SourceFiles. If there is
|
||||
// input, we return it. Otherwise, we return an error.
|
||||
type InputLoader struct {
|
||||
// CheckInConfig contains options for the CheckIn API. If
|
||||
// not set, then we'll create a default config. If set but
|
||||
// there are fields inside it that are not set, then we
|
||||
// will set them to a default value.
|
||||
CheckInConfig *model.CheckInConfig
|
||||
|
||||
// InputPolicy specifies the input policy for the
|
||||
// current experiment. We will not load any input if
|
||||
// the policy says we should not. You MUST fill in
|
||||
// this field.
|
||||
InputPolicy InputPolicy
|
||||
|
||||
// Session is the current measurement session. You
|
||||
// MUST fill in this field.
|
||||
Session InputLoaderSession
|
||||
|
||||
// InputLoaderConfig contains config for InputLoader.
|
||||
type InputLoaderConfig struct {
|
||||
// StaticInputs contains optional input to be added
|
||||
// to the resulting input list if possible.
|
||||
StaticInputs []string
|
||||
|
||||
// SourceFiles contains optional files to read input
|
||||
// from. Each file should contain a single input string
|
||||
// per line. We will fail if any file is unreadable.
|
||||
// per line. We will fail if any file is unreadable
|
||||
// as well as if any file is empty.
|
||||
SourceFiles []string
|
||||
|
||||
// InputPolicy specifies the input policy for the
|
||||
// current experiment. We will not load any input if
|
||||
// the policy says we should not.
|
||||
InputPolicy InputPolicy
|
||||
|
||||
// Session is the current measurement session.
|
||||
Session InputLoaderSession
|
||||
|
||||
// URLLimit is the optional limit on the number of URLs
|
||||
// that probe services should return to us.
|
||||
URLLimit int64
|
||||
|
||||
// URLCategories limits the categories of URLs that
|
||||
// probe services should return to us.
|
||||
URLCategories []string
|
||||
}
|
||||
|
||||
// NewInputLoader creates a new InputLoader.
|
||||
func NewInputLoader(config InputLoaderConfig) InputLoader {
|
||||
// TODO(bassosimone): the current implementation stems from a
|
||||
// simple refactoring from a previous implementation where
|
||||
// we weren't using interfaces. Because now we're using interfaces,
|
||||
// there is the opportunity to select behaviour here depending
|
||||
// on the specified policy rather than later inside Load.
|
||||
return inputLoader{InputLoaderConfig: config}
|
||||
}
|
||||
|
||||
type inputLoader struct {
|
||||
InputLoaderConfig
|
||||
}
|
||||
|
||||
var _ InputLoader = inputLoader{}
|
||||
|
||||
// Load attempts to load input using the specified input loader. We will
|
||||
// return a list of URLs because this is the only input we support.
|
||||
func (il inputLoader) Load(ctx context.Context) ([]model.URLInfo, error) {
|
||||
func (il *InputLoader) Load(ctx context.Context) ([]model.URLInfo, error) {
|
||||
switch il.InputPolicy {
|
||||
case InputOptional:
|
||||
return il.loadOptional()
|
||||
case InputOrQueryTestLists:
|
||||
return il.loadOrQueryTestList(ctx)
|
||||
case InputOrQueryBackend:
|
||||
return il.loadOrQueryBackend(ctx)
|
||||
case InputStrictlyRequired:
|
||||
return il.loadStrictlyRequired(ctx)
|
||||
default:
|
||||
|
@ -115,22 +98,27 @@ func (il inputLoader) Load(ctx context.Context) ([]model.URLInfo, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (il inputLoader) loadNone() ([]model.URLInfo, error) {
|
||||
// loadNone implements the InputNone policy.
|
||||
func (il *InputLoader) loadNone() ([]model.URLInfo, error) {
|
||||
if len(il.StaticInputs) > 0 || len(il.SourceFiles) > 0 {
|
||||
return nil, ErrNoInputExpected
|
||||
}
|
||||
// Note that we need to return a single empty entry.
|
||||
return []model.URLInfo{{}}, nil
|
||||
}
|
||||
|
||||
func (il inputLoader) loadOptional() ([]model.URLInfo, error) {
|
||||
// loadOptional implements the InputOptional policy.
|
||||
func (il *InputLoader) loadOptional() ([]model.URLInfo, error) {
|
||||
inputs, err := il.loadLocal()
|
||||
if err == nil && len(inputs) <= 0 {
|
||||
// Note that we need to return a single empty entry.
|
||||
inputs = []model.URLInfo{{}}
|
||||
}
|
||||
return inputs, err
|
||||
}
|
||||
|
||||
func (il inputLoader) loadStrictlyRequired(ctx context.Context) ([]model.URLInfo, error) {
|
||||
// loadStrictlyRequired implements the InputStrictlyRequired policy.
|
||||
func (il *InputLoader) loadStrictlyRequired(ctx context.Context) ([]model.URLInfo, error) {
|
||||
inputs, err := il.loadLocal()
|
||||
if err != nil || len(inputs) > 0 {
|
||||
return inputs, err
|
||||
|
@ -138,15 +126,17 @@ func (il inputLoader) loadStrictlyRequired(ctx context.Context) ([]model.URLInfo
|
|||
return nil, ErrInputRequired
|
||||
}
|
||||
|
||||
func (il inputLoader) loadOrQueryTestList(ctx context.Context) ([]model.URLInfo, error) {
|
||||
// loadOrQueryBackend implements the InputOrQueryBackend policy.
|
||||
func (il *InputLoader) loadOrQueryBackend(ctx context.Context) ([]model.URLInfo, error) {
|
||||
inputs, err := il.loadLocal()
|
||||
if err != nil || len(inputs) > 0 {
|
||||
return inputs, err
|
||||
}
|
||||
return il.loadRemote(loadRemoteConfig{ctx: ctx, session: il.Session})
|
||||
return il.loadRemote(ctx)
|
||||
}
|
||||
|
||||
func (il inputLoader) loadLocal() ([]model.URLInfo, error) {
|
||||
// loadLocal loads inputs from StaticInputs and SourceFiles.
|
||||
func (il *InputLoader) loadLocal() ([]model.URLInfo, error) {
|
||||
inputs := []model.URLInfo{}
|
||||
for _, input := range il.StaticInputs {
|
||||
inputs = append(inputs, model.URLInfo{URL: input})
|
||||
|
@ -165,7 +155,12 @@ func (il inputLoader) loadLocal() ([]model.URLInfo, error) {
|
|||
return inputs, nil
|
||||
}
|
||||
|
||||
func (il inputLoader) readfile(filepath string, open func(string) (fsx.File, error)) ([]model.URLInfo, error) {
|
||||
// inputLoaderOpenFn is the type of the function to open a file.
|
||||
type inputLoaderOpenFn func(filepath string) (fs.File, error)
|
||||
|
||||
// readfile reads inputs from the specified file. The open argument should be
|
||||
// compatible with stdlib's fs.Open and helps us with unit testing.
|
||||
func (il *InputLoader) readfile(filepath string, open inputLoaderOpenFn) ([]model.URLInfo, error) {
|
||||
inputs := []model.URLInfo{}
|
||||
filep, err := open(filepath)
|
||||
if err != nil {
|
||||
|
@ -188,22 +183,22 @@ func (il inputLoader) readfile(filepath string, open func(string) (fsx.File, err
|
|||
return inputs, nil
|
||||
}
|
||||
|
||||
type loadRemoteConfig struct {
|
||||
ctx context.Context
|
||||
session InputLoaderSession
|
||||
}
|
||||
|
||||
func (il inputLoader) loadRemote(conf loadRemoteConfig) ([]model.URLInfo, error) {
|
||||
if err := conf.session.MaybeLookupLocationContext(conf.ctx); err != nil {
|
||||
return nil, err
|
||||
// loadRemote loads inputs from a remote source.
|
||||
func (il *InputLoader) loadRemote(ctx context.Context) ([]model.URLInfo, error) {
|
||||
config := il.CheckInConfig
|
||||
if config == nil {
|
||||
// Note: Session.CheckIn documentation says it will fill in
|
||||
// any field with a required value with a reasonable default
|
||||
// if such value is missing. So, here we just need to be
|
||||
// concerned about NOT passing it a NULL pointer.
|
||||
config = &model.CheckInConfig{}
|
||||
}
|
||||
client, err := conf.session.NewOrchestraClient(conf.ctx)
|
||||
reply, err := il.Session.CheckIn(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.FetchURLList(conf.ctx, model.URLListConfig{
|
||||
CountryCode: conf.session.ProbeCC(),
|
||||
Limit: il.URLLimit,
|
||||
Categories: il.URLCategories,
|
||||
})
|
||||
if reply.WebConnectivity == nil || len(reply.WebConnectivity.URLs) <= 0 {
|
||||
return nil, ErrNoURLsReturned
|
||||
}
|
||||
return reply.WebConnectivity.URLs, nil
|
||||
}
|
||||
|
|
|
@ -1,316 +0,0 @@
|
|||
package engine_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
engine "github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/kvstore"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
func TestInputLoaderInputNoneWithStaticInputs(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
InputPolicy: engine.InputNone,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithFilesInputs(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputNone,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithBothInputs(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputNone,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithNoInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputNone,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 1 || out[0].URL != "" {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalWithNoInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputOptional,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 1 || out[0].URL != "" {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalWithInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputOptional,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalNonexistentFile(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"/nonexistent",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputOptional,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, syscall.ENOENT) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputStrictlyRequired,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithoutInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputStrictlyRequired,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrInputRequired) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithEmptyFile(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputStrictlyRequired,
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader3.txt", // we want it before inputloader2.txt
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrDetectedEmptyFile) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryTestListsWithInput(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: engine.InputOrQueryTestLists,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryTestListsWithNoInputAndCancelledContext(t *testing.T) {
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "testdata",
|
||||
KVStore: kvstore.NewMemoryKeyValueStore(),
|
||||
Logger: log.Log,
|
||||
SoftwareName: "miniooni",
|
||||
SoftwareVersion: "0.1.0-dev",
|
||||
TempDir: "testdata",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sess.Close()
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputOrQueryTestLists,
|
||||
Session: sess,
|
||||
})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // fail immediately
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryTestListsWithNoInput(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AvailableProbeServices: []model.Service{{
|
||||
Address: "https://ams-pg-test.ooni.org/",
|
||||
Type: "https",
|
||||
}},
|
||||
AssetsDir: "testdata",
|
||||
KVStore: kvstore.NewMemoryKeyValueStore(),
|
||||
Logger: log.Log,
|
||||
SoftwareName: "miniooni",
|
||||
SoftwareVersion: "0.1.0-dev",
|
||||
TempDir: "testdata",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sess.Close()
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputOrQueryTestLists,
|
||||
Session: sess,
|
||||
URLLimit: 30,
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) < 10 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryTestListsWithEmptyFile(t *testing.T) {
|
||||
il := engine.NewInputLoader(engine.InputLoaderConfig{
|
||||
InputPolicy: engine.InputOrQueryTestLists,
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader3.txt", // we want it before inputloader2.txt
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
})
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, engine.ErrDetectedEmptyFile) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
45
internal/engine/inputloader_network_test.go
Normal file
45
internal/engine/inputloader_network_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package engine_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/apex/log"
|
||||
engine "github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/kvstore"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
func TestInputLoaderInputOrQueryBackendWithNoInput(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skip test in short mode")
|
||||
}
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AvailableProbeServices: []model.Service{{
|
||||
Address: "https://ams-pg-test.ooni.org/",
|
||||
Type: "https",
|
||||
}},
|
||||
KVStore: kvstore.NewMemoryKeyValueStore(),
|
||||
Logger: log.Log,
|
||||
SoftwareName: "miniooni",
|
||||
SoftwareVersion: "0.1.0-dev",
|
||||
TempDir: "testdata",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sess.Close()
|
||||
il := &engine.InputLoader{
|
||||
InputPolicy: engine.InputOrQueryBackend,
|
||||
Session: sess,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) < 10 {
|
||||
// check-in SHOULD return AT LEAST 20 URLs at a time.
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
}
|
|
@ -4,17 +4,286 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/internal/fsx"
|
||||
"github.com/apex/log"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/kvstore"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
func TestInputLoaderInputNoneWithStaticInputs(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
InputPolicy: InputNone,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithFilesInputs(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputNone,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithBothInputs(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputNone,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrNoInputExpected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputNoneWithNoInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputNone,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 1 || out[0].URL != "" {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalWithNoInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputOptional,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 1 || out[0].URL != "" {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalWithInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputOptional,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOptionalNonexistentFile(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"/nonexistent",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputOptional,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, syscall.ENOENT) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputStrictlyRequired,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithoutInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputStrictlyRequired,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrInputRequired) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputStrictlyRequiredWithEmptyFile(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputStrictlyRequired,
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader3.txt", // we want it before inputloader2.txt
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrDetectedEmptyFile) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryBackendWithInput(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
StaticInputs: []string{"https://www.google.com/"},
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
InputPolicy: InputOrQueryBackend,
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(out) != 5 {
|
||||
t.Fatal("not the output length we expected")
|
||||
}
|
||||
expect := []model.URLInfo{
|
||||
{URL: "https://www.google.com/"},
|
||||
{URL: "https://www.x.org/"},
|
||||
{URL: "https://www.slashdot.org/"},
|
||||
{URL: "https://abc.xyz/"},
|
||||
{URL: "https://run.ooni.io/"},
|
||||
}
|
||||
if diff := cmp.Diff(out, expect); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryBackendWithNoInputAndCancelledContext(t *testing.T) {
|
||||
sess, err := NewSession(SessionConfig{
|
||||
KVStore: kvstore.NewMemoryKeyValueStore(),
|
||||
Logger: log.Log,
|
||||
SoftwareName: "miniooni",
|
||||
SoftwareVersion: "0.1.0-dev",
|
||||
TempDir: "testdata",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sess.Close()
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputOrQueryBackend,
|
||||
Session: sess,
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // fail immediately
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderInputOrQueryBackendWithEmptyFile(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
InputPolicy: InputOrQueryBackend,
|
||||
SourceFiles: []string{
|
||||
"testdata/inputloader1.txt",
|
||||
"testdata/inputloader3.txt", // we want it before inputloader2.txt
|
||||
"testdata/inputloader2.txt",
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
out, err := il.Load(ctx)
|
||||
if !errors.Is(err, ErrDetectedEmptyFile) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("not the output we expected")
|
||||
}
|
||||
}
|
||||
|
||||
type InputLoaderBrokenFS struct{}
|
||||
|
||||
func (InputLoaderBrokenFS) Open(filepath string) (fsx.File, error) {
|
||||
func (InputLoaderBrokenFS) Open(filepath string) (fs.File, error) {
|
||||
return InputLoaderBrokenFile{}, nil
|
||||
}
|
||||
|
||||
|
@ -33,7 +302,7 @@ func (InputLoaderBrokenFile) Close() error {
|
|||
}
|
||||
|
||||
func TestInputLoaderReadfileScannerFailure(t *testing.T) {
|
||||
il := inputLoader{}
|
||||
il := &InputLoader{}
|
||||
out, err := il.readfile("", InputLoaderBrokenFS{}.Open)
|
||||
if !errors.Is(err, syscall.EFAULT) {
|
||||
t.Fatal("not the error we expected")
|
||||
|
@ -43,64 +312,34 @@ func TestInputLoaderReadfileScannerFailure(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type InputLoaderBrokenSession struct {
|
||||
OrchestraClient model.ExperimentOrchestraClient
|
||||
Error error
|
||||
// InputLoaderMockableSession is a mockable session
|
||||
// used by InputLoader tests.
|
||||
type InputLoaderMockableSession struct {
|
||||
// Output contains the output of CheckIn. It should
|
||||
// be nil when Error is not-nil.
|
||||
Output *model.CheckInInfo
|
||||
|
||||
// Error is the error to be returned by CheckIn. It
|
||||
// should be nil when Output is not-nil.
|
||||
Error error
|
||||
}
|
||||
|
||||
func (InputLoaderBrokenSession) MaybeLookupLocationContext(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ilbs InputLoaderBrokenSession) NewOrchestraClient(ctx context.Context) (model.ExperimentOrchestraClient, error) {
|
||||
if ilbs.OrchestraClient != nil {
|
||||
return ilbs.OrchestraClient, nil
|
||||
// CheckIn implements InputLoaderSession.CheckIn.
|
||||
func (sess *InputLoaderMockableSession) CheckIn(
|
||||
ctx context.Context, config *model.CheckInConfig) (*model.CheckInInfo, error) {
|
||||
if sess.Output == nil && sess.Error == nil {
|
||||
return nil, errors.New("both Output and Error are nil")
|
||||
}
|
||||
return nil, io.EOF
|
||||
return sess.Output, sess.Error
|
||||
}
|
||||
|
||||
func (InputLoaderBrokenSession) ProbeCC() string {
|
||||
return "IT"
|
||||
}
|
||||
|
||||
func TestInputLoaderNewOrchestraClientFailure(t *testing.T) {
|
||||
il := inputLoader{}
|
||||
lrc := loadRemoteConfig{
|
||||
ctx: context.Background(),
|
||||
session: InputLoaderBrokenSession{},
|
||||
}
|
||||
out, err := il.loadRemote(lrc)
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("expected nil output here")
|
||||
}
|
||||
}
|
||||
|
||||
type InputLoaderBrokenOrchestraClient struct{}
|
||||
|
||||
func (InputLoaderBrokenOrchestraClient) FetchPsiphonConfig(ctx context.Context) ([]byte, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (InputLoaderBrokenOrchestraClient) FetchTorTargets(ctx context.Context, cc string) (map[string]model.TorTarget, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (InputLoaderBrokenOrchestraClient) FetchURLList(ctx context.Context, config model.URLListConfig) ([]model.URLInfo, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func TestInputLoaderFetchURLListFailure(t *testing.T) {
|
||||
il := inputLoader{}
|
||||
lrc := loadRemoteConfig{
|
||||
ctx: context.Background(),
|
||||
session: InputLoaderBrokenSession{
|
||||
OrchestraClient: InputLoaderBrokenOrchestraClient{},
|
||||
func TestInputLoaderCheckInFailure(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
Session: &InputLoaderMockableSession{
|
||||
Error: io.EOF,
|
||||
},
|
||||
}
|
||||
out, err := il.loadRemote(lrc)
|
||||
out, err := il.loadRemote(context.Background())
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
|
@ -108,3 +347,63 @@ func TestInputLoaderFetchURLListFailure(t *testing.T) {
|
|||
t.Fatal("expected nil output here")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderCheckInSuccessWithNilWebConnectivity(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
Session: &InputLoaderMockableSession{
|
||||
Output: &model.CheckInInfo{},
|
||||
},
|
||||
}
|
||||
out, err := il.loadRemote(context.Background())
|
||||
if !errors.Is(err, ErrNoURLsReturned) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("expected nil output here")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderCheckInSuccessWithNoURLs(t *testing.T) {
|
||||
il := &InputLoader{
|
||||
Session: &InputLoaderMockableSession{
|
||||
Output: &model.CheckInInfo{
|
||||
WebConnectivity: &model.CheckInInfoWebConnectivity{},
|
||||
},
|
||||
},
|
||||
}
|
||||
out, err := il.loadRemote(context.Background())
|
||||
if !errors.Is(err, ErrNoURLsReturned) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if out != nil {
|
||||
t.Fatal("expected nil output here")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputLoaderCheckInSuccessWithSomeURLs(t *testing.T) {
|
||||
expect := []model.URLInfo{{
|
||||
CategoryCode: "NEWS",
|
||||
CountryCode: "IT",
|
||||
URL: "https://repubblica.it",
|
||||
}, {
|
||||
CategoryCode: "NEWS",
|
||||
CountryCode: "IT",
|
||||
URL: "https://corriere.it",
|
||||
}}
|
||||
il := &InputLoader{
|
||||
Session: &InputLoaderMockableSession{
|
||||
Output: &model.CheckInInfo{
|
||||
WebConnectivity: &model.CheckInInfoWebConnectivity{
|
||||
URLs: expect,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
out, err := il.loadRemote(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(expect, out); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package engine
|
|||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
@ -50,6 +52,12 @@ type InputProcessor struct {
|
|||
// Inputs is the list of inputs to measure.
|
||||
Inputs []model.URLInfo
|
||||
|
||||
// MaxRuntime is the optional maximum runtime
|
||||
// when looping over a list of inputs (e.g. when
|
||||
// running Web Connectivity). Zero means that
|
||||
// there will be no MaxRuntime limit.
|
||||
MaxRuntime time.Duration
|
||||
|
||||
// Options contains command line options for this experiment.
|
||||
Options []string
|
||||
|
||||
|
@ -60,6 +68,11 @@ type InputProcessor struct {
|
|||
// Submitter is the code that will submit measurements
|
||||
// to the OONI collector.
|
||||
Submitter InputProcessorSubmitterWrapper
|
||||
|
||||
// terminatedByMaxRuntime is an internal atomic variabile
|
||||
// incremented when we're terminated by MaxRuntime. We
|
||||
// only use this variable when testing.
|
||||
terminatedByMaxRuntime int32
|
||||
}
|
||||
|
||||
// InputProcessorSaverWrapper is InputProcessor's
|
||||
|
@ -115,8 +128,13 @@ func (ipsw inputProcessorSubmitterWrapper) Submit(
|
|||
// is always causing us to break out of the loop. The user
|
||||
// though is free to choose different policies by configuring
|
||||
// the Experiment, Submitter, and Saver fields properly.
|
||||
func (ip InputProcessor) Run(ctx context.Context) error {
|
||||
func (ip *InputProcessor) Run(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
for idx, url := range ip.Inputs {
|
||||
if ip.MaxRuntime > 0 && time.Since(start) > ip.MaxRuntime {
|
||||
atomic.AddInt32(&ip.terminatedByMaxRuntime, 1)
|
||||
return nil
|
||||
}
|
||||
input := url.URL
|
||||
meas, err := ip.Experiment.MeasureWithContext(ctx, idx, input)
|
||||
if err != nil {
|
||||
|
|
|
@ -4,13 +4,15 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||||
)
|
||||
|
||||
type FakeInputProcessorExperiment struct {
|
||||
Err error
|
||||
M []*model.Measurement
|
||||
SleepTime time.Duration
|
||||
Err error
|
||||
M []*model.Measurement
|
||||
}
|
||||
|
||||
func (fipe *FakeInputProcessorExperiment) MeasureWithContext(
|
||||
|
@ -18,6 +20,9 @@ func (fipe *FakeInputProcessorExperiment) MeasureWithContext(
|
|||
if fipe.Err != nil {
|
||||
return nil, fipe.Err
|
||||
}
|
||||
if fipe.SleepTime > 0 {
|
||||
time.Sleep(fipe.SleepTime)
|
||||
}
|
||||
m := new(model.Measurement)
|
||||
// Here we add annotations to ensure that the input processor
|
||||
// is MERGING annotations as opposed to overwriting them.
|
||||
|
@ -30,7 +35,7 @@ func (fipe *FakeInputProcessorExperiment) MeasureWithContext(
|
|||
|
||||
func TestInputProcessorMeasurementFailed(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
ip := InputProcessor{
|
||||
ip := &InputProcessor{
|
||||
Experiment: NewInputProcessorExperimentWrapper(
|
||||
&FakeInputProcessorExperiment{Err: expected},
|
||||
),
|
||||
|
@ -58,7 +63,7 @@ func (fips *FakeInputProcessorSubmitter) Submit(
|
|||
func TestInputProcessorSubmissionFailed(t *testing.T) {
|
||||
fipe := &FakeInputProcessorExperiment{}
|
||||
expected := errors.New("mocked error")
|
||||
ip := InputProcessor{
|
||||
ip := &InputProcessor{
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
|
@ -108,7 +113,7 @@ func (fips *FakeInputProcessorSaver) SaveMeasurement(m *model.Measurement) error
|
|||
|
||||
func TestInputProcessorSaveOnDiskFailed(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
ip := InputProcessor{
|
||||
ip := &InputProcessor{
|
||||
Experiment: NewInputProcessorExperimentWrapper(
|
||||
&FakeInputProcessorExperiment{},
|
||||
),
|
||||
|
@ -133,7 +138,7 @@ func TestInputProcessorGood(t *testing.T) {
|
|||
fipe := &FakeInputProcessorExperiment{}
|
||||
saver := &FakeInputProcessorSaver{Err: nil}
|
||||
submitter := &FakeInputProcessorSubmitter{Err: nil}
|
||||
ip := InputProcessor{
|
||||
ip := &InputProcessor{
|
||||
Experiment: NewInputProcessorExperimentWrapper(fipe),
|
||||
Inputs: []model.URLInfo{{
|
||||
URL: "https://www.kernel.org/",
|
||||
|
@ -148,6 +153,9 @@ func TestInputProcessorGood(t *testing.T) {
|
|||
if err := ip.Run(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ip.terminatedByMaxRuntime > 0 {
|
||||
t.Fatal("terminated by max runtime!?")
|
||||
}
|
||||
if len(fipe.M) != 2 || len(saver.M) != 2 || len(submitter.M) != 2 {
|
||||
t.Fatal("not all measurements saved")
|
||||
}
|
||||
|
@ -164,3 +172,30 @@ func TestInputProcessorGood(t *testing.T) {
|
|||
t.Fatal("invalid saver.M[1].Input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputProcessorMaxRuntime(t *testing.T) {
|
||||
fipe := &FakeInputProcessorExperiment{
|
||||
SleepTime: 50 * time.Millisecond,
|
||||
}
|
||||
saver := &FakeInputProcessorSaver{Err: nil}
|
||||
submitter := &FakeInputProcessorSubmitter{Err: nil}
|
||||
ip := &InputProcessor{
|
||||
Experiment: NewInputProcessorExperimentWrapper(fipe),
|
||||
Inputs: []model.URLInfo{{
|
||||
URL: "https://www.kernel.org/",
|
||||
}, {
|
||||
URL: "https://www.slashdot.org/",
|
||||
}},
|
||||
MaxRuntime: 1 * time.Nanosecond,
|
||||
Options: []string{"fake=true"},
|
||||
Saver: NewInputProcessorSaverWrapper(saver),
|
||||
Submitter: NewInputProcessorSubmitterWrapper(submitter),
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := ip.Run(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ip.terminatedByMaxRuntime <= 0 {
|
||||
t.Fatal("not terminated by max runtime")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,31 +3,18 @@ package fsx
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// File is a generic file. This interface is taken from the draft
|
||||
// iofs golang design. We'll use fs.File when available.
|
||||
type File interface {
|
||||
Stat() (os.FileInfo, error)
|
||||
Read([]byte) (int, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// FS is a generic file system. Like File, it's adapted from
|
||||
// the draft iofs golang design document.
|
||||
type FS interface {
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
// Open is a wrapper for os.Open that ensures that we're opening a file.
|
||||
func Open(pathname string) (File, error) {
|
||||
func Open(pathname string) (fs.File, error) {
|
||||
return OpenWithFS(filesystem{}, pathname)
|
||||
}
|
||||
|
||||
// OpenWithFS is like Open but with explicit file system argument.
|
||||
func OpenWithFS(fs FS, pathname string) (File, error) {
|
||||
func OpenWithFS(fs fs.FS, pathname string) (fs.File, error) {
|
||||
file, err := fs.Open(pathname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -39,13 +26,16 @@ func OpenWithFS(fs FS, pathname string) (File, error) {
|
|||
}
|
||||
if info.IsDir() {
|
||||
file.Close()
|
||||
return nil, fmt.Errorf("input path points to a directory: %w", syscall.EISDIR)
|
||||
return nil, fmt.Errorf(
|
||||
"input path points to a directory: %w", syscall.EISDIR)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// filesystem is a private implementation of fs.FS.
|
||||
type filesystem struct{}
|
||||
|
||||
func (filesystem) Open(pathname string) (File, error) {
|
||||
// Open implements fs.FS.Open.
|
||||
func (filesystem) Open(pathname string) (fs.File, error) {
|
||||
return os.Open(pathname)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package fsx_test
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
@ -26,8 +27,8 @@ func (FailingStatFile) Stat() (os.FileInfo, error) {
|
|||
return nil, errStatFailed
|
||||
}
|
||||
|
||||
func (fs FailingStatFS) Open(pathname string) (fsx.File, error) {
|
||||
return FailingStatFile{CloseCount: fs.CloseCount}, nil
|
||||
func (f FailingStatFS) Open(pathname string) (fs.File, error) {
|
||||
return FailingStatFile(f), nil
|
||||
}
|
||||
|
||||
func (fs FailingStatFile) Close() error {
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
|
||||
// Session allows to mock sessions.
|
||||
type Session struct {
|
||||
MockableASNDatabasePath string
|
||||
MockableTestHelpers map[string][]model.Service
|
||||
MockableHTTPClient *http.Client
|
||||
MockableLogger model.Logger
|
||||
|
@ -39,11 +38,6 @@ type Session struct {
|
|||
MockableUserAgent string
|
||||
}
|
||||
|
||||
// ASNDatabasePath implements ExperimentSession.ASNDatabasePath
|
||||
func (sess *Session) ASNDatabasePath() string {
|
||||
return sess.MockableASNDatabasePath
|
||||
}
|
||||
|
||||
// GetTestHelpersByName implements ExperimentSession.GetTestHelpersByName
|
||||
func (sess *Session) GetTestHelpersByName(name string) ([]model.Service, bool) {
|
||||
services, okay := sess.MockableTestHelpers[name]
|
||||
|
@ -162,6 +156,8 @@ var _ torx.Session = &Session{}
|
|||
// ExperimentOrchestraClient is the experiment's view of
|
||||
// a client for querying the OONI orchestra.
|
||||
type ExperimentOrchestraClient struct {
|
||||
MockableCheckInInfo *model.CheckInInfo
|
||||
MockableCheckInErr error
|
||||
MockableFetchPsiphonConfigResult []byte
|
||||
MockableFetchPsiphonConfigErr error
|
||||
MockableFetchTorTargetsResult map[string]model.TorTarget
|
||||
|
@ -170,6 +166,12 @@ type ExperimentOrchestraClient struct {
|
|||
MockableFetchURLListErr error
|
||||
}
|
||||
|
||||
// CheckIn implements ExperimentOrchestraClient.CheckIn.
|
||||
func (c ExperimentOrchestraClient) CheckIn(
|
||||
ctx context.Context, config model.CheckInConfig) (*model.CheckInInfo, error) {
|
||||
return c.MockableCheckInInfo, c.MockableCheckInErr
|
||||
}
|
||||
|
||||
// FetchPsiphonConfig implements ExperimentOrchestraClient.FetchPsiphonConfig
|
||||
func (c ExperimentOrchestraClient) FetchPsiphonConfig(
|
||||
ctx context.Context) ([]byte, error) {
|
||||
|
|
|
@ -17,7 +17,6 @@ func TestStartWithCancelledContext(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "../../testdata",
|
||||
Logger: log.Log,
|
||||
SoftwareName: "ooniprobe-engine",
|
||||
SoftwareVersion: "0.0.1",
|
||||
|
@ -39,7 +38,6 @@ func TestStartStop(t *testing.T) {
|
|||
t.Skip("skip test in short mode")
|
||||
}
|
||||
sess, err := engine.NewSession(engine.SessionConfig{
|
||||
AssetsDir: "../../testdata",
|
||||
Logger: log.Log,
|
||||
SoftwareName: "ooniprobe-engine",
|
||||
SoftwareVersion: "0.0.1",
|
||||
|
|
|
@ -21,7 +21,7 @@ const systemResolverURL = "system:///"
|
|||
|
||||
// allmakers contains all the makers in a list. We use the http3
|
||||
// prefix to indicate we wanna use http3. The code will translate
|
||||
// this to https and set the proper next options.
|
||||
// this to https and set the proper netx options.
|
||||
var allmakers = []*resolvermaker{{
|
||||
url: "https://cloudflare-dns.com/dns-query",
|
||||
}, {
|
||||
|
@ -97,7 +97,7 @@ func (r *Resolver) newresolver(URL string) (childResolver, error) {
|
|||
func (r *Resolver) getresolver(URL string) (childResolver, error) {
|
||||
defer r.mu.Unlock()
|
||||
r.mu.Lock()
|
||||
if re, found := r.res[URL]; found == true {
|
||||
if re, found := r.res[URL]; found {
|
||||
return re, nil // already created
|
||||
}
|
||||
re, err := r.newresolver(URL)
|
||||
|
|
|
@ -17,6 +17,10 @@
|
|||
// but it will of course be the most popular resolver if anything else
|
||||
// is failing us. (We will still occasionally probe for other working
|
||||
// resolvers and increase their score on success.)
|
||||
//
|
||||
// We also support a socks5 proxy. When such a proxy is configured,
|
||||
// the code WILL skip http3 resolvers AS WELL AS the system
|
||||
// resolver, in an attempt to avoid leaking your queries.
|
||||
package sessionresolver
|
||||
|
||||
import (
|
||||
|
@ -34,18 +38,60 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/runtimex"
|
||||
)
|
||||
|
||||
// Resolver is the session resolver. You should create an instance of
|
||||
// this structure and use it in session.go.
|
||||
// Resolver is the session resolver. Resolver will try to use
|
||||
// a bunch of DoT/DoH resolvers before falling back to the
|
||||
// system resolver. The relative priorities of the resolver
|
||||
// are stored onto the KVStore such that we can remember them
|
||||
// and therefore we can generally give preference to underlying
|
||||
// DoT/DoH resolvers that work better.
|
||||
//
|
||||
// You MUST NOT modify public fields of this structure once it
|
||||
// has been created, because that MAY lead to data races.
|
||||
//
|
||||
// You should create an instance of this structure and use
|
||||
// it in internal/engine/session.go.
|
||||
type Resolver struct {
|
||||
ByteCounter *bytecounter.Counter // optional
|
||||
KVStore KVStore // optional
|
||||
Logger Logger // optional
|
||||
ProxyURL *url.URL // optional
|
||||
codec codec
|
||||
// ByteCounter is the optional byte counter. It will count
|
||||
// the bytes used by any child resolver except for the
|
||||
// system resolver, whose bytes ARE NOT counted. If this
|
||||
// field is not set, then we won't count the bytes.
|
||||
ByteCounter *bytecounter.Counter
|
||||
|
||||
// KVStore is the optional key-value store where you
|
||||
// want us to write statistics about which resolver is
|
||||
// working better in your network. If this field is
|
||||
// not set, then we'll use a in-memory store.
|
||||
KVStore KVStore
|
||||
|
||||
// Logger is the optional logger you want us to use
|
||||
// to emit log messages.
|
||||
Logger Logger
|
||||
|
||||
// ProxyURL is the optional URL of the socks5 proxy
|
||||
// we should be using. If not set, then we WON'T use
|
||||
// any proxy. If set, then we WON'T use any http3
|
||||
// based resolvers and we WON'T use the system resolver.
|
||||
ProxyURL *url.URL
|
||||
|
||||
// codec is the optional codec to use. If not set, we
|
||||
// will construct a default codec.
|
||||
codec codec
|
||||
|
||||
// dnsClientMaker is the optional dnsclientmaker to
|
||||
// use. If not set, we will use the default.
|
||||
dnsClientMaker dnsclientmaker
|
||||
mu sync.Mutex
|
||||
once sync.Once
|
||||
res map[string]childResolver
|
||||
|
||||
// mu provides synchronisation of internal fields.
|
||||
mu sync.Mutex
|
||||
|
||||
// once ensures that CloseIdleConnection is
|
||||
// run just once.
|
||||
once sync.Once
|
||||
|
||||
// res maps a URL to a child resolver. We will
|
||||
// construct child resolvers just once and we
|
||||
// will track them into this field.
|
||||
res map[string]childResolver
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes the idle connections, if any. This
|
||||
|
@ -73,7 +119,8 @@ func (r *Resolver) LookupHost(ctx context.Context, hostname string) ([]string, e
|
|||
defer r.writestate(state)
|
||||
me := multierror.New(ErrLookupHost)
|
||||
for _, e := range state {
|
||||
if r.shouldSkipWithProxy(e) {
|
||||
if r.ProxyURL != nil && r.shouldSkipWithProxy(e) {
|
||||
r.logger().Infof("sessionresolver: skipping with proxy: %+v", e)
|
||||
continue // we cannot proxy this URL so ignore it
|
||||
}
|
||||
addrs, err := r.lookupHost(ctx, e, hostname)
|
||||
|
|
|
@ -294,7 +294,7 @@ func TestResolverWorksWithProxy(t *testing.T) {
|
|||
<-done
|
||||
// check results
|
||||
if !errors.Is(err, ErrLookupHost) {
|
||||
t.Fatal("not the error we expected")
|
||||
t.Fatal("not the error we expected", err)
|
||||
}
|
||||
if addrs != nil {
|
||||
t.Fatal("expected nil addrs")
|
||||
|
|
62
internal/engine/legacy/assetsdir/assetsdir.go
Normal file
62
internal/engine/legacy/assetsdir/assetsdir.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Package assetsdir contains code to cleanup the assets dir. We removed
|
||||
// the assetsdir in the 3.9.0 development cycle.
|
||||
package assetsdir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ErrEmptyDir indicates that you passed to Cleanup an empty dir.
|
||||
var ErrEmptyDir = errors.New("empty assets directory")
|
||||
|
||||
// Result is the result of a Cleanup run.
|
||||
type Result struct {
|
||||
// ASNDatabaseErr is the error of deleting the
|
||||
// file containing the old ASN database.
|
||||
ASNDatabaseErr error
|
||||
|
||||
// CABundleErr is the error of deleting the file
|
||||
// containing the old CA bundle.
|
||||
CABundleErr error
|
||||
|
||||
// CountryDatabaseErr is the error of deleting the
|
||||
// file containing the old country database.
|
||||
CountryDatabaseErr error
|
||||
|
||||
// RmdirErr is the error of deleting the supposedly
|
||||
// empty directory that contained assets.
|
||||
RmdirErr error
|
||||
}
|
||||
|
||||
// Cleanup removes data from the assetsdir. This function will
|
||||
// try to delete the known assets inside of dir. It then also
|
||||
// tries to delete the directory. If the directory is not empty,
|
||||
// this operation will fail. That means the user has put some
|
||||
// extra data in there and we don't want to remove it.
|
||||
//
|
||||
// Returns the Result of cleaning up the assets on success and
|
||||
// an error on failure. The only cause of error is passing to
|
||||
// this function an empty directory. The Result data structure
|
||||
// contains the result of each individual remove operation.
|
||||
func Cleanup(dir string) (*Result, error) {
|
||||
return fcleanup(dir, os.Remove)
|
||||
}
|
||||
|
||||
// fcleanup is a version of Cleanup where we can mock the real function
|
||||
// used for removing files and dirs, so we can write unit tests.
|
||||
func fcleanup(dir string, remove func(name string) error) (*Result, error) {
|
||||
if dir == "" {
|
||||
return nil, ErrEmptyDir
|
||||
}
|
||||
r := &Result{}
|
||||
asndb := filepath.Join(dir, "asn.mmdb")
|
||||
r.ASNDatabaseErr = os.Remove(asndb)
|
||||
cabundle := filepath.Join(dir, "ca-bundle.pem")
|
||||
r.CABundleErr = os.Remove(cabundle)
|
||||
countrydb := filepath.Join(dir, "country.mmdb")
|
||||
r.CountryDatabaseErr = os.Remove(countrydb)
|
||||
r.RmdirErr = os.Remove(dir)
|
||||
return r, nil
|
||||
}
|
40
internal/engine/legacy/assetsdir/assetsdir_test.go
Normal file
40
internal/engine/legacy/assetsdir/assetsdir_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package assetsdir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCleanupNormalUsage(t *testing.T) {
|
||||
result, err := Cleanup("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// we expect a bunch of ENOENT because the directory does not exist.
|
||||
isExpectedErr := func(err error) bool {
|
||||
return err != nil && strings.HasSuffix(err.Error(), "no such file or directory")
|
||||
}
|
||||
if !isExpectedErr(result.ASNDatabaseErr) {
|
||||
t.Fatal("unexpected error", result.ASNDatabaseErr)
|
||||
}
|
||||
if !isExpectedErr(result.CABundleErr) {
|
||||
t.Fatal("unexpected error", result.CABundleErr)
|
||||
}
|
||||
if !isExpectedErr(result.CountryDatabaseErr) {
|
||||
t.Fatal("unexpected error", result.CountryDatabaseErr)
|
||||
}
|
||||
if !isExpectedErr(result.RmdirErr) {
|
||||
t.Fatal("unexpected error", result.RmdirErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupWithEmptyInput(t *testing.T) {
|
||||
result, err := Cleanup("")
|
||||
if !errors.Is(err, ErrEmptyDir) {
|
||||
t.Fatal("unexpected error", err)
|
||||
}
|
||||
if result != nil {
|
||||
t.Fatal("expected nil result")
|
||||
}
|
||||
}
|
|
@ -101,6 +101,9 @@ func TestDialerSetCABundleWAI(t *testing.T) {
|
|||
func TestDialerForceSpecificSNI(t *testing.T) {
|
||||
dialer := netx.NewDialer()
|
||||
err := dialer.ForceSpecificSNI("www.facebook.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, err := dialer.DialTLS("tcp", "www.google.com:443")
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
|
|
|
@ -56,7 +56,7 @@ func TestMeasurementRootWithMeasurementRootPanic(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
ctx := context.Background()
|
||||
ctx = WithMeasurementRoot(ctx, nil)
|
||||
_ = WithMeasurementRoot(ctx, nil)
|
||||
}
|
||||
|
||||
func TestErrWrapperPublicAPI(t *testing.T) {
|
||||
|
|
|
@ -8,14 +8,22 @@ import (
|
|||
// ExperimentOrchestraClient is the experiment's view of
|
||||
// a client for querying the OONI orchestra API.
|
||||
type ExperimentOrchestraClient interface {
|
||||
// CheckIn calls the check-in API.
|
||||
CheckIn(ctx context.Context, config CheckInConfig) (*CheckInInfo, error)
|
||||
|
||||
// FetchPsiphonConfig returns psiphon config from the API.
|
||||
FetchPsiphonConfig(ctx context.Context) ([]byte, error)
|
||||
|
||||
// FetchTorTargets returns tor targets from the API.
|
||||
FetchTorTargets(ctx context.Context, cc string) (map[string]TorTarget, error)
|
||||
|
||||
// FetchURLList returns URLs from the API.
|
||||
// This method is deprecated and will be removed soon.
|
||||
FetchURLList(ctx context.Context, config URLListConfig) ([]URLInfo, error)
|
||||
}
|
||||
|
||||
// ExperimentSession is the experiment's view of a session.
|
||||
type ExperimentSession interface {
|
||||
ASNDatabasePath() string
|
||||
GetTestHelpersByName(name string) ([]Service, bool)
|
||||
DefaultHTTPClient() *http.Client
|
||||
Logger() Logger
|
||||
|
|
|
@ -397,7 +397,7 @@ type DNSQueryEntry struct {
|
|||
type dnsQueryType string
|
||||
|
||||
// NewDNSQueriesList returns a list of DNS queries.
|
||||
func NewDNSQueriesList(begin time.Time, events []trace.Event, dbpath string) []DNSQueryEntry {
|
||||
func NewDNSQueriesList(begin time.Time, events []trace.Event) []DNSQueryEntry {
|
||||
// TODO(bassosimone): add support for CNAME lookups.
|
||||
var out []DNSQueryEntry
|
||||
for _, ev := range events {
|
||||
|
@ -409,7 +409,7 @@ func NewDNSQueriesList(begin time.Time, events []trace.Event, dbpath string) []D
|
|||
for _, addr := range ev.Addresses {
|
||||
if qtype.ipoftype(addr) {
|
||||
entry.Answers = append(
|
||||
entry.Answers, qtype.makeanswerentry(addr, dbpath))
|
||||
entry.Answers, qtype.makeanswerentry(addr))
|
||||
}
|
||||
}
|
||||
if len(entry.Answers) <= 0 && ev.Err == nil {
|
||||
|
@ -431,16 +431,16 @@ func NewDNSQueriesList(begin time.Time, events []trace.Event, dbpath string) []D
|
|||
func (qtype dnsQueryType) ipoftype(addr string) bool {
|
||||
switch qtype {
|
||||
case "A":
|
||||
return strings.Contains(addr, ":") == false
|
||||
return !strings.Contains(addr, ":")
|
||||
case "AAAA":
|
||||
return strings.Contains(addr, ":") == true
|
||||
return strings.Contains(addr, ":")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (qtype dnsQueryType) makeanswerentry(addr string, dbpath string) DNSAnswerEntry {
|
||||
func (qtype dnsQueryType) makeanswerentry(addr string) DNSAnswerEntry {
|
||||
answer := DNSAnswerEntry{AnswerType: string(qtype)}
|
||||
asn, org, _ := geolocate.LookupASN(dbpath, addr)
|
||||
asn, org, _ := geolocate.LookupASN(addr)
|
||||
answer.ASN = int64(asn)
|
||||
answer.ASOrgName = org
|
||||
switch qtype {
|
||||
|
@ -463,17 +463,20 @@ func (qtype dnsQueryType) makequeryentry(begin time.Time, ev trace.Event) DNSQue
|
|||
}
|
||||
}
|
||||
|
||||
// NetworkEvent is a network event.
|
||||
// NetworkEvent is a network event. It contains all the possible fields
|
||||
// and most fields are optional. They are only added when it makes sense
|
||||
// for them to be there _and_ we have data to show.
|
||||
type NetworkEvent struct {
|
||||
Address string `json:"address,omitempty"`
|
||||
ConnID int64 `json:"conn_id,omitempty"`
|
||||
DialID int64 `json:"dial_id,omitempty"`
|
||||
Failure *string `json:"failure"`
|
||||
NumBytes int64 `json:"num_bytes,omitempty"`
|
||||
Operation string `json:"operation"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
T float64 `json:"t"`
|
||||
TransactionID int64 `json:"transaction_id,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
ConnID int64 `json:"conn_id,omitempty"`
|
||||
DialID int64 `json:"dial_id,omitempty"`
|
||||
Failure *string `json:"failure"`
|
||||
NumBytes int64 `json:"num_bytes,omitempty"`
|
||||
Operation string `json:"operation"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
T float64 `json:"t"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
TransactionID int64 `json:"transaction_id,omitempty"`
|
||||
}
|
||||
|
||||
// NewNetworkEventsList returns a list of DNS queries.
|
||||
|
@ -547,6 +550,7 @@ type TLSHandshake struct {
|
|||
PeerCertificates []MaybeBinaryValue `json:"peer_certificates"`
|
||||
ServerName string `json:"server_name"`
|
||||
T float64 `json:"t"`
|
||||
Tags []string `json:"tags"`
|
||||
TLSVersion string `json:"tls_version"`
|
||||
TransactionID int64 `json:"transaction_id,omitempty"`
|
||||
}
|
||||
|
|
41
internal/engine/netx/archival/archival_internal_test.go
Normal file
41
internal/engine/netx/archival/archival_internal_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package archival
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDNSQueryIPOfType(t *testing.T) {
|
||||
type expectation struct {
|
||||
qtype dnsQueryType
|
||||
ip string
|
||||
output bool
|
||||
}
|
||||
var expectations = []expectation{{
|
||||
qtype: "A",
|
||||
ip: "8.8.8.8",
|
||||
output: true,
|
||||
}, {
|
||||
qtype: "A",
|
||||
ip: "2a00:1450:4002:801::2004",
|
||||
output: false,
|
||||
}, {
|
||||
qtype: "AAAA",
|
||||
ip: "8.8.8.8",
|
||||
output: false,
|
||||
}, {
|
||||
qtype: "AAAA",
|
||||
ip: "2a00:1450:4002:801::2004",
|
||||
output: true,
|
||||
}, {
|
||||
qtype: "ANTANI",
|
||||
ip: "2a00:1450:4002:801::2004",
|
||||
output: false,
|
||||
}, {
|
||||
qtype: "ANTANI",
|
||||
ip: "8.8.8.8",
|
||||
output: false,
|
||||
}}
|
||||
for _, exp := range expectations {
|
||||
if exp.qtype.ipoftype(exp.ip) != exp.output {
|
||||
t.Fatalf("failure for %+v", exp)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/errorx"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/resourcesmanager"
|
||||
)
|
||||
|
||||
func TestNewTCPConnectList(t *testing.T) {
|
||||
|
@ -285,15 +284,10 @@ func TestNewRequestList(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewDNSQueriesList(t *testing.T) {
|
||||
err := (&resourcesmanager.CopyWorker{DestDir: "../../testdata"}).Ensure()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
begin := time.Now()
|
||||
type args struct {
|
||||
begin time.Time
|
||||
events []trace.Event
|
||||
dbpath string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -334,9 +328,13 @@ func TestNewDNSQueriesList(t *testing.T) {
|
|||
},
|
||||
want: []archival.DNSQueryEntry{{
|
||||
Answers: []archival.DNSAnswerEntry{{
|
||||
ASN: 15169,
|
||||
ASOrgName: "Google LLC",
|
||||
AnswerType: "A",
|
||||
IPv4: "8.8.8.8",
|
||||
}, {
|
||||
ASN: 15169,
|
||||
ASOrgName: "Google LLC",
|
||||
AnswerType: "A",
|
||||
IPv4: "8.8.4.4",
|
||||
}},
|
||||
|
@ -357,27 +355,6 @@ func TestNewDNSQueriesList(t *testing.T) {
|
|||
Time: begin.Add(200 * time.Millisecond),
|
||||
}},
|
||||
},
|
||||
want: []archival.DNSQueryEntry{{
|
||||
Answers: []archival.DNSAnswerEntry{{
|
||||
AnswerType: "AAAA",
|
||||
IPv6: "2001:4860:4860::8888",
|
||||
}},
|
||||
Hostname: "dns.google.com",
|
||||
QueryType: "AAAA",
|
||||
T: 0.2,
|
||||
}},
|
||||
}, {
|
||||
name: "run with ASN DB",
|
||||
args: args{
|
||||
begin: begin,
|
||||
events: []trace.Event{{
|
||||
Addresses: []string{"2001:4860:4860::8888"},
|
||||
Hostname: "dns.google.com",
|
||||
Name: "resolve_done",
|
||||
Time: begin.Add(200 * time.Millisecond),
|
||||
}},
|
||||
dbpath: "../../testdata/asn.mmdb",
|
||||
},
|
||||
want: []archival.DNSQueryEntry{{
|
||||
Answers: []archival.DNSAnswerEntry{{
|
||||
ASN: 15169,
|
||||
|
@ -399,7 +376,6 @@ func TestNewDNSQueriesList(t *testing.T) {
|
|||
Name: "resolve_done",
|
||||
Time: begin.Add(200 * time.Millisecond),
|
||||
}},
|
||||
dbpath: "../../testdata/asn.mmdb",
|
||||
},
|
||||
want: []archival.DNSQueryEntry{{
|
||||
Answers: nil,
|
||||
|
@ -419,9 +395,9 @@ func TestNewDNSQueriesList(t *testing.T) {
|
|||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := archival.NewDNSQueriesList(
|
||||
tt.args.begin, tt.args.events, tt.args.dbpath); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Error(cmp.Diff(got, tt.want))
|
||||
got := archival.NewDNSQueriesList(tt.args.begin, tt.args.events)
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1009,13 +985,6 @@ func TestNewFailure(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDNSQueryTypeInvalidIPOfType(t *testing.T) {
|
||||
qtype := archival.DNSQueryType("ANTANI")
|
||||
if qtype.IPOfType("8.8.8.8") != false {
|
||||
t.Fatal("unexpected return value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFailedOperation(t *testing.T) {
|
||||
type args struct {
|
||||
err error
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package archival
|
||||
|
||||
// DNSQueryType allows to access dnsQueryType from unit tests
|
||||
type DNSQueryType = dnsQueryType
|
||||
|
||||
func (qtype dnsQueryType) IPOfType(addr string) bool {
|
||||
return qtype.ipoftype(addr)
|
||||
}
|
|
@ -175,6 +175,9 @@ func TestToFailureString(t *testing.T) {
|
|||
defer cancel() // fail immediately
|
||||
udpAddr := &net.UDPAddr{IP: net.ParseIP("216.58.212.164"), Port: 80, Zone: ""}
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sess, err := quic.DialEarlyContext(ctx, udpConn, udpAddr, "google.com:80", &tls.Config{}, &quic.Config{})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error here")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-03-03 11:48:43.129132377 +0100 CET m=+2.301468593
|
||||
// 2021-03-31 16:50:03.221493757 +0200 CEST m=+1.318280953
|
||||
// https://curl.haxx.se/ca/cacert.pem
|
||||
|
||||
package gocertifi
|
||||
|
@ -3241,7 +3241,7 @@ kpzNNIaRkPpkUZ3+/uul9XXeifdy
|
|||
`
|
||||
|
||||
// CACerts builds an X.509 certificate pool containing the
|
||||
// certificate bundle from https://curl.haxx.se/ca/cacert.pem fetch on 2021-03-03 11:48:43.129132377 +0100 CET m=+2.301468593.
|
||||
// certificate bundle from https://curl.haxx.se/ca/cacert.pem fetch on 2021-03-31 16:50:03.221493757 +0200 CEST m=+1.318280953.
|
||||
// Returns nil on error along with an appropriate error code.
|
||||
func CACerts() (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// +build !go1.15
|
||||
|
||||
package quicdialer
|
||||
|
||||
import (
|
||||
|
@ -10,5 +8,5 @@ import (
|
|||
|
||||
// ConnectionState returns the ConnectionState of a QUIC Session.
|
||||
func ConnectionState(sess quic.EarlySession) tls.ConnectionState {
|
||||
return tls.ConnectionState{}
|
||||
return sess.ConnectionState().TLS.ConnectionState
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// +build go1.15
|
||||
|
||||
package quicdialer
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
)
|
||||
|
||||
// ConnectionState returns the ConnectionState of a QUIC Session.
|
||||
func ConnectionState(sess quic.EarlySession) tls.ConnectionState {
|
||||
return sess.ConnectionState().ConnectionState
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:50.431349269 +0100 CET m=+0.000196051
|
||||
// 2021-03-31 16:50:03.708459546 +0200 CEST m=+0.000228783
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -12,8 +12,8 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
// CheckReportIDAPI implements the CheckReportID API.
|
||||
type CheckReportIDAPI struct {
|
||||
// simpleCheckReportIDAPI implements the CheckReportID API.
|
||||
type simpleCheckReportIDAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -21,28 +21,28 @@ type CheckReportIDAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *CheckReportIDAPI) baseURL() string {
|
||||
func (api *simpleCheckReportIDAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *CheckReportIDAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleCheckReportIDAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *CheckReportIDAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleCheckReportIDAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *CheckReportIDAPI) httpClient() HTTPClient {
|
||||
func (api *simpleCheckReportIDAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func (api *CheckReportIDAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the CheckReportID API.
|
||||
func (api *CheckReportIDAPI) Call(ctx context.Context, req *apimodel.CheckReportIDRequest) (*apimodel.CheckReportIDResponse, error) {
|
||||
func (api *simpleCheckReportIDAPI) Call(ctx context.Context, req *apimodel.CheckReportIDRequest) (*apimodel.CheckReportIDResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -62,8 +62,8 @@ func (api *CheckReportIDAPI) Call(ctx context.Context, req *apimodel.CheckReport
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// CheckInAPI implements the CheckIn API.
|
||||
type CheckInAPI struct {
|
||||
// simpleCheckInAPI implements the CheckIn API.
|
||||
type simpleCheckInAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -71,28 +71,28 @@ type CheckInAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *CheckInAPI) baseURL() string {
|
||||
func (api *simpleCheckInAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *CheckInAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleCheckInAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *CheckInAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleCheckInAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *CheckInAPI) httpClient() HTTPClient {
|
||||
func (api *simpleCheckInAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func (api *CheckInAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the CheckIn API.
|
||||
func (api *CheckInAPI) Call(ctx context.Context, req *apimodel.CheckInRequest) (*apimodel.CheckInResponse, error) {
|
||||
func (api *simpleCheckInAPI) Call(ctx context.Context, req *apimodel.CheckInRequest) (*apimodel.CheckInResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -112,8 +112,8 @@ func (api *CheckInAPI) Call(ctx context.Context, req *apimodel.CheckInRequest) (
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// LoginAPI implements the Login API.
|
||||
type LoginAPI struct {
|
||||
// simpleLoginAPI implements the Login API.
|
||||
type simpleLoginAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -121,28 +121,28 @@ type LoginAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *LoginAPI) baseURL() string {
|
||||
func (api *simpleLoginAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *LoginAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleLoginAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *LoginAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleLoginAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *LoginAPI) httpClient() HTTPClient {
|
||||
func (api *simpleLoginAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ func (api *LoginAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the Login API.
|
||||
func (api *LoginAPI) Call(ctx context.Context, req *apimodel.LoginRequest) (*apimodel.LoginResponse, error) {
|
||||
func (api *simpleLoginAPI) Call(ctx context.Context, req *apimodel.LoginRequest) (*apimodel.LoginResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -162,8 +162,8 @@ func (api *LoginAPI) Call(ctx context.Context, req *apimodel.LoginRequest) (*api
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// MeasurementMetaAPI implements the MeasurementMeta API.
|
||||
type MeasurementMetaAPI struct {
|
||||
// simpleMeasurementMetaAPI implements the MeasurementMeta API.
|
||||
type simpleMeasurementMetaAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -171,28 +171,28 @@ type MeasurementMetaAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *MeasurementMetaAPI) baseURL() string {
|
||||
func (api *simpleMeasurementMetaAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *MeasurementMetaAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleMeasurementMetaAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *MeasurementMetaAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleMeasurementMetaAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *MeasurementMetaAPI) httpClient() HTTPClient {
|
||||
func (api *simpleMeasurementMetaAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ func (api *MeasurementMetaAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the MeasurementMeta API.
|
||||
func (api *MeasurementMetaAPI) Call(ctx context.Context, req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
func (api *simpleMeasurementMetaAPI) Call(ctx context.Context, req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -212,8 +212,8 @@ func (api *MeasurementMetaAPI) Call(ctx context.Context, req *apimodel.Measureme
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// RegisterAPI implements the Register API.
|
||||
type RegisterAPI struct {
|
||||
// simpleRegisterAPI implements the Register API.
|
||||
type simpleRegisterAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -221,28 +221,28 @@ type RegisterAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *RegisterAPI) baseURL() string {
|
||||
func (api *simpleRegisterAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *RegisterAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleRegisterAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *RegisterAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleRegisterAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *RegisterAPI) httpClient() HTTPClient {
|
||||
func (api *simpleRegisterAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ func (api *RegisterAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the Register API.
|
||||
func (api *RegisterAPI) Call(ctx context.Context, req *apimodel.RegisterRequest) (*apimodel.RegisterResponse, error) {
|
||||
func (api *simpleRegisterAPI) Call(ctx context.Context, req *apimodel.RegisterRequest) (*apimodel.RegisterResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -262,8 +262,8 @@ func (api *RegisterAPI) Call(ctx context.Context, req *apimodel.RegisterRequest)
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// TestHelpersAPI implements the TestHelpers API.
|
||||
type TestHelpersAPI struct {
|
||||
// simpleTestHelpersAPI implements the TestHelpers API.
|
||||
type simpleTestHelpersAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -271,28 +271,28 @@ type TestHelpersAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *TestHelpersAPI) baseURL() string {
|
||||
func (api *simpleTestHelpersAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *TestHelpersAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleTestHelpersAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *TestHelpersAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleTestHelpersAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *TestHelpersAPI) httpClient() HTTPClient {
|
||||
func (api *simpleTestHelpersAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ func (api *TestHelpersAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the TestHelpers API.
|
||||
func (api *TestHelpersAPI) Call(ctx context.Context, req *apimodel.TestHelpersRequest) (apimodel.TestHelpersResponse, error) {
|
||||
func (api *simpleTestHelpersAPI) Call(ctx context.Context, req *apimodel.TestHelpersRequest) (apimodel.TestHelpersResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -312,8 +312,8 @@ func (api *TestHelpersAPI) Call(ctx context.Context, req *apimodel.TestHelpersRe
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// PsiphonConfigAPI implements the PsiphonConfig API.
|
||||
type PsiphonConfigAPI struct {
|
||||
// simplePsiphonConfigAPI implements the PsiphonConfig API.
|
||||
type simplePsiphonConfigAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -324,8 +324,8 @@ type PsiphonConfigAPI struct {
|
|||
|
||||
// WithToken returns a copy of the API where the
|
||||
// value of the Token field is replaced with token.
|
||||
func (api *PsiphonConfigAPI) WithToken(token string) PsiphonConfigCaller {
|
||||
out := &PsiphonConfigAPI{}
|
||||
func (api *simplePsiphonConfigAPI) WithToken(token string) callerForPsiphonConfigAPI {
|
||||
out := &simplePsiphonConfigAPI{}
|
||||
out.BaseURL = api.BaseURL
|
||||
out.HTTPClient = api.HTTPClient
|
||||
out.JSONCodec = api.JSONCodec
|
||||
|
@ -335,28 +335,28 @@ func (api *PsiphonConfigAPI) WithToken(token string) PsiphonConfigCaller {
|
|||
return out
|
||||
}
|
||||
|
||||
func (api *PsiphonConfigAPI) baseURL() string {
|
||||
func (api *simplePsiphonConfigAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *PsiphonConfigAPI) requestMaker() RequestMaker {
|
||||
func (api *simplePsiphonConfigAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *PsiphonConfigAPI) jsonCodec() JSONCodec {
|
||||
func (api *simplePsiphonConfigAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *PsiphonConfigAPI) httpClient() HTTPClient {
|
||||
func (api *simplePsiphonConfigAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ func (api *PsiphonConfigAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the PsiphonConfig API.
|
||||
func (api *PsiphonConfigAPI) Call(ctx context.Context, req *apimodel.PsiphonConfigRequest) (apimodel.PsiphonConfigResponse, error) {
|
||||
func (api *simplePsiphonConfigAPI) Call(ctx context.Context, req *apimodel.PsiphonConfigRequest) (apimodel.PsiphonConfigResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -380,8 +380,8 @@ func (api *PsiphonConfigAPI) Call(ctx context.Context, req *apimodel.PsiphonConf
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// TorTargetsAPI implements the TorTargets API.
|
||||
type TorTargetsAPI struct {
|
||||
// simpleTorTargetsAPI implements the TorTargets API.
|
||||
type simpleTorTargetsAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -392,8 +392,8 @@ type TorTargetsAPI struct {
|
|||
|
||||
// WithToken returns a copy of the API where the
|
||||
// value of the Token field is replaced with token.
|
||||
func (api *TorTargetsAPI) WithToken(token string) TorTargetsCaller {
|
||||
out := &TorTargetsAPI{}
|
||||
func (api *simpleTorTargetsAPI) WithToken(token string) callerForTorTargetsAPI {
|
||||
out := &simpleTorTargetsAPI{}
|
||||
out.BaseURL = api.BaseURL
|
||||
out.HTTPClient = api.HTTPClient
|
||||
out.JSONCodec = api.JSONCodec
|
||||
|
@ -403,28 +403,28 @@ func (api *TorTargetsAPI) WithToken(token string) TorTargetsCaller {
|
|||
return out
|
||||
}
|
||||
|
||||
func (api *TorTargetsAPI) baseURL() string {
|
||||
func (api *simpleTorTargetsAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *TorTargetsAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleTorTargetsAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *TorTargetsAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleTorTargetsAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *TorTargetsAPI) httpClient() HTTPClient {
|
||||
func (api *simpleTorTargetsAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -432,7 +432,7 @@ func (api *TorTargetsAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the TorTargets API.
|
||||
func (api *TorTargetsAPI) Call(ctx context.Context, req *apimodel.TorTargetsRequest) (apimodel.TorTargetsResponse, error) {
|
||||
func (api *simpleTorTargetsAPI) Call(ctx context.Context, req *apimodel.TorTargetsRequest) (apimodel.TorTargetsResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -448,8 +448,8 @@ func (api *TorTargetsAPI) Call(ctx context.Context, req *apimodel.TorTargetsRequ
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// URLsAPI implements the URLs API.
|
||||
type URLsAPI struct {
|
||||
// simpleURLsAPI implements the URLs API.
|
||||
type simpleURLsAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -457,28 +457,28 @@ type URLsAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *URLsAPI) baseURL() string {
|
||||
func (api *simpleURLsAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *URLsAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleURLsAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *URLsAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleURLsAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *URLsAPI) httpClient() HTTPClient {
|
||||
func (api *simpleURLsAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ func (api *URLsAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the URLs API.
|
||||
func (api *URLsAPI) Call(ctx context.Context, req *apimodel.URLsRequest) (*apimodel.URLsResponse, error) {
|
||||
func (api *simpleURLsAPI) Call(ctx context.Context, req *apimodel.URLsRequest) (*apimodel.URLsResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -498,8 +498,8 @@ func (api *URLsAPI) Call(ctx context.Context, req *apimodel.URLsRequest) (*apimo
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// OpenReportAPI implements the OpenReport API.
|
||||
type OpenReportAPI struct {
|
||||
// simpleOpenReportAPI implements the OpenReport API.
|
||||
type simpleOpenReportAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
|
@ -507,28 +507,28 @@ type OpenReportAPI struct {
|
|||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *OpenReportAPI) baseURL() string {
|
||||
func (api *simpleOpenReportAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *OpenReportAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleOpenReportAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *OpenReportAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleOpenReportAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *OpenReportAPI) httpClient() HTTPClient {
|
||||
func (api *simpleOpenReportAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -536,7 +536,7 @@ func (api *OpenReportAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the OpenReport API.
|
||||
func (api *OpenReportAPI) Call(ctx context.Context, req *apimodel.OpenReportRequest) (*apimodel.OpenReportResponse, error) {
|
||||
func (api *simpleOpenReportAPI) Call(ctx context.Context, req *apimodel.OpenReportRequest) (*apimodel.OpenReportResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -548,45 +548,45 @@ func (api *OpenReportAPI) Call(ctx context.Context, req *apimodel.OpenReportRequ
|
|||
return api.newResponse(api.httpClient().Do(httpReq))
|
||||
}
|
||||
|
||||
// SubmitMeasurementAPI implements the SubmitMeasurement API.
|
||||
type SubmitMeasurementAPI struct {
|
||||
// simpleSubmitMeasurementAPI implements the SubmitMeasurement API.
|
||||
type simpleSubmitMeasurementAPI struct {
|
||||
BaseURL string // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
RequestMaker RequestMaker // optional
|
||||
TemplateExecutor TemplateExecutor // optional
|
||||
TemplateExecutor templateExecutor // optional
|
||||
UserAgent string // optional
|
||||
}
|
||||
|
||||
func (api *SubmitMeasurementAPI) baseURL() string {
|
||||
func (api *simpleSubmitMeasurementAPI) baseURL() string {
|
||||
if api.BaseURL != "" {
|
||||
return api.BaseURL
|
||||
}
|
||||
return "https://ps1.ooni.io"
|
||||
}
|
||||
|
||||
func (api *SubmitMeasurementAPI) requestMaker() RequestMaker {
|
||||
func (api *simpleSubmitMeasurementAPI) requestMaker() RequestMaker {
|
||||
if api.RequestMaker != nil {
|
||||
return api.RequestMaker
|
||||
}
|
||||
return &defaultRequestMaker{}
|
||||
}
|
||||
|
||||
func (api *SubmitMeasurementAPI) jsonCodec() JSONCodec {
|
||||
func (api *simpleSubmitMeasurementAPI) jsonCodec() JSONCodec {
|
||||
if api.JSONCodec != nil {
|
||||
return api.JSONCodec
|
||||
}
|
||||
return &defaultJSONCodec{}
|
||||
}
|
||||
|
||||
func (api *SubmitMeasurementAPI) templateExecutor() TemplateExecutor {
|
||||
func (api *simpleSubmitMeasurementAPI) templateExecutor() templateExecutor {
|
||||
if api.TemplateExecutor != nil {
|
||||
return api.TemplateExecutor
|
||||
}
|
||||
return &defaultTemplateExecutor{}
|
||||
}
|
||||
|
||||
func (api *SubmitMeasurementAPI) httpClient() HTTPClient {
|
||||
func (api *simpleSubmitMeasurementAPI) httpClient() HTTPClient {
|
||||
if api.HTTPClient != nil {
|
||||
return api.HTTPClient
|
||||
}
|
||||
|
@ -594,7 +594,7 @@ func (api *SubmitMeasurementAPI) httpClient() HTTPClient {
|
|||
}
|
||||
|
||||
// Call calls the SubmitMeasurement API.
|
||||
func (api *SubmitMeasurementAPI) Call(ctx context.Context, req *apimodel.SubmitMeasurementRequest) (*apimodel.SubmitMeasurementResponse, error) {
|
||||
func (api *simpleSubmitMeasurementAPI) Call(ctx context.Context, req *apimodel.SubmitMeasurementRequest) (*apimodel.SubmitMeasurementResponse, error) {
|
||||
httpReq, err := api.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:50.81792142 +0100 CET m=+0.000095792
|
||||
// 2021-03-31 16:50:04.057051721 +0200 CEST m=+0.000093878
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -22,7 +22,7 @@ import (
|
|||
)
|
||||
|
||||
func TestCheckReportIDInvalidURL(t *testing.T) {
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -41,7 +41,7 @@ func TestCheckReportIDInvalidURL(t *testing.T) {
|
|||
func TestCheckReportIDWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -59,7 +59,7 @@ func TestCheckReportIDWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestCheckReportIDWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -77,7 +77,7 @@ func TestCheckReportIDWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestCheckReportIDWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -95,7 +95,7 @@ func TestCheckReportIDWith401(t *testing.T) {
|
|||
|
||||
func TestCheckReportIDWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -117,7 +117,7 @@ func TestCheckReportIDWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -139,7 +139,7 @@ func TestCheckReportIDWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func TestCheckReportIDRoundTrip(t *testing.T) {
|
|||
req := &apimodel.CheckReportIDRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &CheckReportIDAPI{BaseURL: srvr.URL}
|
||||
api := &simpleCheckReportIDAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -252,7 +252,7 @@ func TestCheckReportIDMandatoryFields(t *testing.T) {
|
|||
clnt := &FakeHTTPClient{Resp: &http.Response{
|
||||
StatusCode: 500,
|
||||
}}
|
||||
api := &CheckReportIDAPI{
|
||||
api := &simpleCheckReportIDAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -267,7 +267,7 @@ func TestCheckReportIDMandatoryFields(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCheckInInvalidURL(t *testing.T) {
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -286,7 +286,7 @@ func TestCheckInInvalidURL(t *testing.T) {
|
|||
func TestCheckInWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -304,7 +304,7 @@ func TestCheckInWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestCheckInMarshalErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
JSONCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -322,7 +322,7 @@ func TestCheckInMarshalErr(t *testing.T) {
|
|||
|
||||
func TestCheckInWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -340,7 +340,7 @@ func TestCheckInWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestCheckInWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -358,7 +358,7 @@ func TestCheckInWith401(t *testing.T) {
|
|||
|
||||
func TestCheckInWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -380,7 +380,7 @@ func TestCheckInWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -402,7 +402,7 @@ func TestCheckInWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &CheckInAPI{
|
||||
api := &simpleCheckInAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ func TestCheckInRoundTrip(t *testing.T) {
|
|||
req := &apimodel.CheckInRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &CheckInAPI{BaseURL: srvr.URL}
|
||||
api := &simpleCheckInAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -512,7 +512,7 @@ func TestCheckInRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoginInvalidURL(t *testing.T) {
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -531,7 +531,7 @@ func TestLoginInvalidURL(t *testing.T) {
|
|||
func TestLoginWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -549,7 +549,7 @@ func TestLoginWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestLoginMarshalErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
JSONCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -567,7 +567,7 @@ func TestLoginMarshalErr(t *testing.T) {
|
|||
|
||||
func TestLoginWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -585,7 +585,7 @@ func TestLoginWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestLoginWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -603,7 +603,7 @@ func TestLoginWith401(t *testing.T) {
|
|||
|
||||
func TestLoginWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -625,7 +625,7 @@ func TestLoginWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -647,7 +647,7 @@ func TestLoginWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &LoginAPI{
|
||||
api := &simpleLoginAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -717,7 +717,7 @@ func TestLoginRoundTrip(t *testing.T) {
|
|||
req := &apimodel.LoginRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &LoginAPI{BaseURL: srvr.URL}
|
||||
api := &simpleLoginAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -757,7 +757,7 @@ func TestLoginRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMeasurementMetaInvalidURL(t *testing.T) {
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -776,7 +776,7 @@ func TestMeasurementMetaInvalidURL(t *testing.T) {
|
|||
func TestMeasurementMetaWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -794,7 +794,7 @@ func TestMeasurementMetaWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestMeasurementMetaWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -812,7 +812,7 @@ func TestMeasurementMetaWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestMeasurementMetaWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -830,7 +830,7 @@ func TestMeasurementMetaWith401(t *testing.T) {
|
|||
|
||||
func TestMeasurementMetaWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -852,7 +852,7 @@ func TestMeasurementMetaWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -874,7 +874,7 @@ func TestMeasurementMetaWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -944,7 +944,7 @@ func TestMeasurementMetaRoundTrip(t *testing.T) {
|
|||
req := &apimodel.MeasurementMetaRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &MeasurementMetaAPI{BaseURL: srvr.URL}
|
||||
api := &simpleMeasurementMetaAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -987,7 +987,7 @@ func TestMeasurementMetaMandatoryFields(t *testing.T) {
|
|||
clnt := &FakeHTTPClient{Resp: &http.Response{
|
||||
StatusCode: 500,
|
||||
}}
|
||||
api := &MeasurementMetaAPI{
|
||||
api := &simpleMeasurementMetaAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1002,7 +1002,7 @@ func TestMeasurementMetaMandatoryFields(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRegisterInvalidURL(t *testing.T) {
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1021,7 +1021,7 @@ func TestRegisterInvalidURL(t *testing.T) {
|
|||
func TestRegisterWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1039,7 +1039,7 @@ func TestRegisterWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestRegisterMarshalErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
JSONCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1057,7 +1057,7 @@ func TestRegisterMarshalErr(t *testing.T) {
|
|||
|
||||
func TestRegisterWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1075,7 +1075,7 @@ func TestRegisterWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestRegisterWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1093,7 +1093,7 @@ func TestRegisterWith401(t *testing.T) {
|
|||
|
||||
func TestRegisterWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1115,7 +1115,7 @@ func TestRegisterWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1137,7 +1137,7 @@ func TestRegisterWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &RegisterAPI{
|
||||
api := &simpleRegisterAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -1207,7 +1207,7 @@ func TestRegisterRoundTrip(t *testing.T) {
|
|||
req := &apimodel.RegisterRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &RegisterAPI{BaseURL: srvr.URL}
|
||||
api := &simpleRegisterAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -1247,7 +1247,7 @@ func TestRegisterRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTestHelpersInvalidURL(t *testing.T) {
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1266,7 +1266,7 @@ func TestTestHelpersInvalidURL(t *testing.T) {
|
|||
func TestTestHelpersWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1284,7 +1284,7 @@ func TestTestHelpersWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestTestHelpersWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1302,7 +1302,7 @@ func TestTestHelpersWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestTestHelpersWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1320,7 +1320,7 @@ func TestTestHelpersWith401(t *testing.T) {
|
|||
|
||||
func TestTestHelpersWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1342,7 +1342,7 @@ func TestTestHelpersWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1364,7 +1364,7 @@ func TestTestHelpersWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -1434,7 +1434,7 @@ func TestTestHelpersRoundTrip(t *testing.T) {
|
|||
req := &apimodel.TestHelpersRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &TestHelpersAPI{BaseURL: srvr.URL}
|
||||
api := &simpleTestHelpersAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -1478,7 +1478,7 @@ func TestTestHelpersResponseLiteralNull(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`null`)},
|
||||
}}
|
||||
api := &TestHelpersAPI{
|
||||
api := &simpleTestHelpersAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1495,7 +1495,7 @@ func TestTestHelpersResponseLiteralNull(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPsiphonConfigInvalidURL(t *testing.T) {
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1512,7 +1512,7 @@ func TestPsiphonConfigInvalidURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPsiphonConfigWithMissingToken(t *testing.T) {
|
||||
api := &PsiphonConfigAPI{} // no token
|
||||
api := &simplePsiphonConfigAPI{} // no token
|
||||
ctx := context.Background()
|
||||
req := &apimodel.PsiphonConfigRequest{}
|
||||
ff := &fakeFill{}
|
||||
|
@ -1529,7 +1529,7 @@ func TestPsiphonConfigWithMissingToken(t *testing.T) {
|
|||
func TestPsiphonConfigWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1548,7 +1548,7 @@ func TestPsiphonConfigWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestPsiphonConfigWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1567,7 +1567,7 @@ func TestPsiphonConfigWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestPsiphonConfigWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1586,7 +1586,7 @@ func TestPsiphonConfigWith401(t *testing.T) {
|
|||
|
||||
func TestPsiphonConfigWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1609,7 +1609,7 @@ func TestPsiphonConfigWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1632,7 +1632,7 @@ func TestPsiphonConfigWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
Token: "fakeToken",
|
||||
|
@ -1703,7 +1703,7 @@ func TestPsiphonConfigRoundTrip(t *testing.T) {
|
|||
req := &apimodel.PsiphonConfigRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &PsiphonConfigAPI{BaseURL: srvr.URL}
|
||||
api := &simplePsiphonConfigAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
ff.fill(&api.Token)
|
||||
// issue request
|
||||
|
@ -1748,7 +1748,7 @@ func TestPsiphonConfigResponseLiteralNull(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`null`)},
|
||||
}}
|
||||
api := &PsiphonConfigAPI{
|
||||
api := &simplePsiphonConfigAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1766,7 +1766,7 @@ func TestPsiphonConfigResponseLiteralNull(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTorTargetsInvalidURL(t *testing.T) {
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -1783,7 +1783,7 @@ func TestTorTargetsInvalidURL(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTorTargetsWithMissingToken(t *testing.T) {
|
||||
api := &TorTargetsAPI{} // no token
|
||||
api := &simpleTorTargetsAPI{} // no token
|
||||
ctx := context.Background()
|
||||
req := &apimodel.TorTargetsRequest{}
|
||||
ff := &fakeFill{}
|
||||
|
@ -1800,7 +1800,7 @@ func TestTorTargetsWithMissingToken(t *testing.T) {
|
|||
func TestTorTargetsWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1819,7 +1819,7 @@ func TestTorTargetsWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestTorTargetsWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1838,7 +1838,7 @@ func TestTorTargetsWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestTorTargetsWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1857,7 +1857,7 @@ func TestTorTargetsWith401(t *testing.T) {
|
|||
|
||||
func TestTorTargetsWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1880,7 +1880,7 @@ func TestTorTargetsWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -1903,7 +1903,7 @@ func TestTorTargetsWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
Token: "fakeToken",
|
||||
|
@ -1974,7 +1974,7 @@ func TestTorTargetsRoundTrip(t *testing.T) {
|
|||
req := &apimodel.TorTargetsRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &TorTargetsAPI{BaseURL: srvr.URL}
|
||||
api := &simpleTorTargetsAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
ff.fill(&api.Token)
|
||||
// issue request
|
||||
|
@ -2019,7 +2019,7 @@ func TestTorTargetsResponseLiteralNull(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`null`)},
|
||||
}}
|
||||
api := &TorTargetsAPI{
|
||||
api := &simpleTorTargetsAPI{
|
||||
HTTPClient: clnt,
|
||||
Token: "fakeToken",
|
||||
}
|
||||
|
@ -2037,7 +2037,7 @@ func TestTorTargetsResponseLiteralNull(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestURLsInvalidURL(t *testing.T) {
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2056,7 +2056,7 @@ func TestURLsInvalidURL(t *testing.T) {
|
|||
func TestURLsWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2074,7 +2074,7 @@ func TestURLsWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestURLsWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2092,7 +2092,7 @@ func TestURLsWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestURLsWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2110,7 +2110,7 @@ func TestURLsWith401(t *testing.T) {
|
|||
|
||||
func TestURLsWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2132,7 +2132,7 @@ func TestURLsWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2154,7 +2154,7 @@ func TestURLsWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &URLsAPI{
|
||||
api := &simpleURLsAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -2224,7 +2224,7 @@ func TestURLsRoundTrip(t *testing.T) {
|
|||
req := &apimodel.URLsRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &URLsAPI{BaseURL: srvr.URL}
|
||||
api := &simpleURLsAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -2264,7 +2264,7 @@ func TestURLsRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOpenReportInvalidURL(t *testing.T) {
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2283,7 +2283,7 @@ func TestOpenReportInvalidURL(t *testing.T) {
|
|||
func TestOpenReportWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2301,7 +2301,7 @@ func TestOpenReportWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestOpenReportMarshalErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
JSONCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2319,7 +2319,7 @@ func TestOpenReportMarshalErr(t *testing.T) {
|
|||
|
||||
func TestOpenReportWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2337,7 +2337,7 @@ func TestOpenReportWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestOpenReportWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2355,7 +2355,7 @@ func TestOpenReportWith401(t *testing.T) {
|
|||
|
||||
func TestOpenReportWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2377,7 +2377,7 @@ func TestOpenReportWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2399,7 +2399,7 @@ func TestOpenReportWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &OpenReportAPI{
|
||||
api := &simpleOpenReportAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -2469,7 +2469,7 @@ func TestOpenReportRoundTrip(t *testing.T) {
|
|||
req := &apimodel.OpenReportRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &OpenReportAPI{BaseURL: srvr.URL}
|
||||
api := &simpleOpenReportAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -2509,7 +2509,7 @@ func TestOpenReportRoundTrip(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSubmitMeasurementInvalidURL(t *testing.T) {
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
BaseURL: "\t", // invalid
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2528,7 +2528,7 @@ func TestSubmitMeasurementInvalidURL(t *testing.T) {
|
|||
func TestSubmitMeasurementWithHTTPErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
clnt := &FakeHTTPClient{Err: errMocked}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2546,7 +2546,7 @@ func TestSubmitMeasurementWithHTTPErr(t *testing.T) {
|
|||
|
||||
func TestSubmitMeasurementMarshalErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
JSONCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2564,7 +2564,7 @@ func TestSubmitMeasurementMarshalErr(t *testing.T) {
|
|||
|
||||
func TestSubmitMeasurementWithNewRequestErr(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
RequestMaker: &FakeRequestMaker{Err: errMocked},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2582,7 +2582,7 @@ func TestSubmitMeasurementWithNewRequestErr(t *testing.T) {
|
|||
|
||||
func TestSubmitMeasurementWith401(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 401}}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2600,7 +2600,7 @@ func TestSubmitMeasurementWith401(t *testing.T) {
|
|||
|
||||
func TestSubmitMeasurementWith400(t *testing.T) {
|
||||
clnt := &FakeHTTPClient{Resp: &http.Response{StatusCode: 400}}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2622,7 +2622,7 @@ func TestSubmitMeasurementWithResponseBodyReadErr(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Err: errMocked},
|
||||
}}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
@ -2644,7 +2644,7 @@ func TestSubmitMeasurementWithUnmarshalFailure(t *testing.T) {
|
|||
StatusCode: 200,
|
||||
Body: &FakeBody{Data: []byte(`{}`)},
|
||||
}}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
JSONCodec: &FakeCodec{DecodeErr: errMocked},
|
||||
}
|
||||
|
@ -2714,7 +2714,7 @@ func TestSubmitMeasurementRoundTrip(t *testing.T) {
|
|||
req := &apimodel.SubmitMeasurementRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
api := &SubmitMeasurementAPI{BaseURL: srvr.URL}
|
||||
api := &simpleSubmitMeasurementAPI{BaseURL: srvr.URL}
|
||||
ff.fill(&api.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
|
@ -2758,7 +2758,7 @@ func TestSubmitMeasurementTemplateErr(t *testing.T) {
|
|||
clnt := &FakeHTTPClient{Resp: &http.Response{
|
||||
StatusCode: 500,
|
||||
}}
|
||||
api := &SubmitMeasurementAPI{
|
||||
api := &simpleSubmitMeasurementAPI{
|
||||
HTTPClient: clnt,
|
||||
TemplateExecutor: &FakeTemplateExecutor{Err: errMocked},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:51.194159684 +0100 CET m=+0.000175181
|
||||
// 2021-03-31 16:50:04.317557242 +0200 CEST m=+0.000161626
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -12,20 +12,20 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
// MeasurementMetaCache implements caching for MeasurementMetaAPI.
|
||||
type MeasurementMetaCache struct {
|
||||
API MeasurementMetaCaller // mandatory
|
||||
GobCodec GobCodec // optional
|
||||
KVStore KVStore // mandatory
|
||||
// withCacheMeasurementMetaAPI implements caching for simpleMeasurementMetaAPI.
|
||||
type withCacheMeasurementMetaAPI struct {
|
||||
API callerForMeasurementMetaAPI // mandatory
|
||||
GobCodec GobCodec // optional
|
||||
KVStore KVStore // mandatory
|
||||
}
|
||||
|
||||
type cacheEntryForMeasurementMeta struct {
|
||||
type cacheEntryForMeasurementMetaAPI struct {
|
||||
Req *apimodel.MeasurementMetaRequest
|
||||
Resp *apimodel.MeasurementMetaResponse
|
||||
}
|
||||
|
||||
// Call calls the API and implements caching.
|
||||
func (c *MeasurementMetaCache) Call(ctx context.Context, req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
func (c *withCacheMeasurementMetaAPI) Call(ctx context.Context, req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
if resp, _ := c.readcache(req); resp != nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
@ -39,26 +39,26 @@ func (c *MeasurementMetaCache) Call(ctx context.Context, req *apimodel.Measureme
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *MeasurementMetaCache) gobCodec() GobCodec {
|
||||
func (c *withCacheMeasurementMetaAPI) gobCodec() GobCodec {
|
||||
if c.GobCodec != nil {
|
||||
return c.GobCodec
|
||||
}
|
||||
return &defaultGobCodec{}
|
||||
}
|
||||
|
||||
func (c *MeasurementMetaCache) getcache() ([]cacheEntryForMeasurementMeta, error) {
|
||||
func (c *withCacheMeasurementMetaAPI) getcache() ([]cacheEntryForMeasurementMetaAPI, error) {
|
||||
data, err := c.KVStore.Get("MeasurementMeta.cache")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []cacheEntryForMeasurementMeta
|
||||
var out []cacheEntryForMeasurementMetaAPI
|
||||
if err := c.gobCodec().Decode(data, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *MeasurementMetaCache) setcache(in []cacheEntryForMeasurementMeta) error {
|
||||
func (c *withCacheMeasurementMetaAPI) setcache(in []cacheEntryForMeasurementMetaAPI) error {
|
||||
data, err := c.gobCodec().Encode(in)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -66,7 +66,7 @@ func (c *MeasurementMetaCache) setcache(in []cacheEntryForMeasurementMeta) error
|
|||
return c.KVStore.Set("MeasurementMeta.cache", data)
|
||||
}
|
||||
|
||||
func (c *MeasurementMetaCache) readcache(req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
func (c *withCacheMeasurementMetaAPI) readcache(req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error) {
|
||||
cache, err := c.getcache()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -79,9 +79,9 @@ func (c *MeasurementMetaCache) readcache(req *apimodel.MeasurementMetaRequest) (
|
|||
return nil, errCacheNotFound
|
||||
}
|
||||
|
||||
func (c *MeasurementMetaCache) writecache(req *apimodel.MeasurementMetaRequest, resp *apimodel.MeasurementMetaResponse) error {
|
||||
func (c *withCacheMeasurementMetaAPI) writecache(req *apimodel.MeasurementMetaRequest, resp *apimodel.MeasurementMetaResponse) error {
|
||||
cache, _ := c.getcache()
|
||||
out := []cacheEntryForMeasurementMeta{{Req: req, Resp: resp}}
|
||||
out := []cacheEntryForMeasurementMetaAPI{{Req: req, Resp: resp}}
|
||||
const toomany = 64
|
||||
for idx, cur := range cache {
|
||||
if reflect.DeepEqual(req, cur.Req) {
|
||||
|
@ -95,4 +95,4 @@ func (c *MeasurementMetaCache) writecache(req *apimodel.MeasurementMetaRequest,
|
|||
return c.setcache(out)
|
||||
}
|
||||
|
||||
var _ MeasurementMetaCaller = &MeasurementMetaCache{}
|
||||
var _ callerForMeasurementMetaAPI = &withCacheMeasurementMetaAPI{}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:51.49660021 +0100 CET m=+0.000217672
|
||||
// 2021-03-31 16:50:04.580341787 +0200 CEST m=+0.000148802
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -14,15 +14,15 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
func TestCacheMeasurementMetaAPISuccess(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPISuccess(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
var expect *apimodel.MeasurementMetaResponse
|
||||
ff.fill(&expect)
|
||||
cache := &MeasurementMetaCache{
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
API: &FakeMeasurementMetaAPI{
|
||||
Response: expect,
|
||||
},
|
||||
KVStore: &memkvstore{},
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
var req *apimodel.MeasurementMetaRequest
|
||||
ff.fill(&req)
|
||||
|
@ -39,12 +39,12 @@ func TestCacheMeasurementMetaAPISuccess(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPIWriteCacheError(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPIWriteCacheError(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
ff := &fakeFill{}
|
||||
var expect *apimodel.MeasurementMetaResponse
|
||||
ff.fill(&expect)
|
||||
cache := &MeasurementMetaCache{
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
API: &FakeMeasurementMetaAPI{
|
||||
Response: expect,
|
||||
},
|
||||
|
@ -62,14 +62,14 @@ func TestCacheMeasurementMetaAPIWriteCacheError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPIFailureWithNoCache(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPIFailureWithNoCache(t *testing.T) {
|
||||
errMocked := errors.New("mocked error")
|
||||
ff := &fakeFill{}
|
||||
cache := &MeasurementMetaCache{
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
API: &FakeMeasurementMetaAPI{
|
||||
Err: errMocked,
|
||||
},
|
||||
KVStore: &memkvstore{},
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
var req *apimodel.MeasurementMetaRequest
|
||||
ff.fill(&req)
|
||||
|
@ -83,16 +83,16 @@ func TestCacheMeasurementMetaAPIFailureWithNoCache(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPIFailureWithPreviousCache(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPIFailureWithPreviousCache(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
var expect *apimodel.MeasurementMetaResponse
|
||||
ff.fill(&expect)
|
||||
fakeapi := &FakeMeasurementMetaAPI{
|
||||
Response: expect,
|
||||
}
|
||||
cache := &MeasurementMetaCache{
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
API: fakeapi,
|
||||
KVStore: &memkvstore{},
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
var req *apimodel.MeasurementMetaRequest
|
||||
ff.fill(&req)
|
||||
|
@ -127,12 +127,12 @@ func TestCacheMeasurementMetaAPIFailureWithPreviousCache(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPISetcacheWithEncodeError(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPISetcacheWithEncodeError(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
errMocked := errors.New("mocked error")
|
||||
var in []cacheEntryForMeasurementMeta
|
||||
var in []cacheEntryForMeasurementMetaAPI
|
||||
ff.fill(&in)
|
||||
cache := &MeasurementMetaCache{
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
GobCodec: &FakeCodec{EncodeErr: errMocked},
|
||||
}
|
||||
err := cache.setcache(in)
|
||||
|
@ -141,12 +141,12 @@ func TestCacheMeasurementMetaAPISetcacheWithEncodeError(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPIReadCacheNotFound(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPIReadCacheNotFound(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
var incache []cacheEntryForMeasurementMeta
|
||||
var incache []cacheEntryForMeasurementMetaAPI
|
||||
ff.fill(&incache)
|
||||
cache := &MeasurementMetaCache{
|
||||
KVStore: &memkvstore{},
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
err := cache.setcache(incache)
|
||||
if err != nil {
|
||||
|
@ -163,7 +163,7 @@ func TestCacheMeasurementMetaAPIReadCacheNotFound(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPIWriteCacheDuplicate(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPIWriteCacheDuplicate(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
var req *apimodel.MeasurementMetaRequest
|
||||
ff.fill(&req)
|
||||
|
@ -171,8 +171,8 @@ func TestCacheMeasurementMetaAPIWriteCacheDuplicate(t *testing.T) {
|
|||
ff.fill(&resp1)
|
||||
var resp2 *apimodel.MeasurementMetaResponse
|
||||
ff.fill(&resp2)
|
||||
cache := &MeasurementMetaCache{
|
||||
KVStore: &memkvstore{},
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
err := cache.writecache(req, resp1)
|
||||
if err != nil {
|
||||
|
@ -194,10 +194,10 @@ func TestCacheMeasurementMetaAPIWriteCacheDuplicate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCacheMeasurementMetaAPICacheSizeLimited(t *testing.T) {
|
||||
func TestCachesimpleMeasurementMetaAPICacheSizeLimited(t *testing.T) {
|
||||
ff := &fakeFill{}
|
||||
cache := &MeasurementMetaCache{
|
||||
KVStore: &memkvstore{},
|
||||
cache := &withCacheMeasurementMetaAPI{
|
||||
KVStore: &MemKVStore{},
|
||||
}
|
||||
var prev int
|
||||
for {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:51.773813223 +0100 CET m=+0.000114768
|
||||
// 2021-03-31 16:50:04.79291318 +0200 CEST m=+0.000077728
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -11,68 +11,68 @@ import (
|
|||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
// CheckReportIDCaller represents any type exposing a method
|
||||
// like CheckReportIDAPI.Call.
|
||||
type CheckReportIDCaller interface {
|
||||
// callerForCheckReportIDAPI represents any type exposing a method
|
||||
// like simpleCheckReportIDAPI.Call.
|
||||
type callerForCheckReportIDAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.CheckReportIDRequest) (*apimodel.CheckReportIDResponse, error)
|
||||
}
|
||||
|
||||
// CheckInCaller represents any type exposing a method
|
||||
// like CheckInAPI.Call.
|
||||
type CheckInCaller interface {
|
||||
// callerForCheckInAPI represents any type exposing a method
|
||||
// like simpleCheckInAPI.Call.
|
||||
type callerForCheckInAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.CheckInRequest) (*apimodel.CheckInResponse, error)
|
||||
}
|
||||
|
||||
// LoginCaller represents any type exposing a method
|
||||
// like LoginAPI.Call.
|
||||
type LoginCaller interface {
|
||||
// callerForLoginAPI represents any type exposing a method
|
||||
// like simpleLoginAPI.Call.
|
||||
type callerForLoginAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.LoginRequest) (*apimodel.LoginResponse, error)
|
||||
}
|
||||
|
||||
// MeasurementMetaCaller represents any type exposing a method
|
||||
// like MeasurementMetaAPI.Call.
|
||||
type MeasurementMetaCaller interface {
|
||||
// callerForMeasurementMetaAPI represents any type exposing a method
|
||||
// like simpleMeasurementMetaAPI.Call.
|
||||
type callerForMeasurementMetaAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.MeasurementMetaRequest) (*apimodel.MeasurementMetaResponse, error)
|
||||
}
|
||||
|
||||
// RegisterCaller represents any type exposing a method
|
||||
// like RegisterAPI.Call.
|
||||
type RegisterCaller interface {
|
||||
// callerForRegisterAPI represents any type exposing a method
|
||||
// like simpleRegisterAPI.Call.
|
||||
type callerForRegisterAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.RegisterRequest) (*apimodel.RegisterResponse, error)
|
||||
}
|
||||
|
||||
// TestHelpersCaller represents any type exposing a method
|
||||
// like TestHelpersAPI.Call.
|
||||
type TestHelpersCaller interface {
|
||||
// callerForTestHelpersAPI represents any type exposing a method
|
||||
// like simpleTestHelpersAPI.Call.
|
||||
type callerForTestHelpersAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.TestHelpersRequest) (apimodel.TestHelpersResponse, error)
|
||||
}
|
||||
|
||||
// PsiphonConfigCaller represents any type exposing a method
|
||||
// like PsiphonConfigAPI.Call.
|
||||
type PsiphonConfigCaller interface {
|
||||
// callerForPsiphonConfigAPI represents any type exposing a method
|
||||
// like simplePsiphonConfigAPI.Call.
|
||||
type callerForPsiphonConfigAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.PsiphonConfigRequest) (apimodel.PsiphonConfigResponse, error)
|
||||
}
|
||||
|
||||
// TorTargetsCaller represents any type exposing a method
|
||||
// like TorTargetsAPI.Call.
|
||||
type TorTargetsCaller interface {
|
||||
// callerForTorTargetsAPI represents any type exposing a method
|
||||
// like simpleTorTargetsAPI.Call.
|
||||
type callerForTorTargetsAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.TorTargetsRequest) (apimodel.TorTargetsResponse, error)
|
||||
}
|
||||
|
||||
// URLsCaller represents any type exposing a method
|
||||
// like URLsAPI.Call.
|
||||
type URLsCaller interface {
|
||||
// callerForURLsAPI represents any type exposing a method
|
||||
// like simpleURLsAPI.Call.
|
||||
type callerForURLsAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.URLsRequest) (*apimodel.URLsResponse, error)
|
||||
}
|
||||
|
||||
// OpenReportCaller represents any type exposing a method
|
||||
// like OpenReportAPI.Call.
|
||||
type OpenReportCaller interface {
|
||||
// callerForOpenReportAPI represents any type exposing a method
|
||||
// like simpleOpenReportAPI.Call.
|
||||
type callerForOpenReportAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.OpenReportRequest) (*apimodel.OpenReportResponse, error)
|
||||
}
|
||||
|
||||
// SubmitMeasurementCaller represents any type exposing a method
|
||||
// like SubmitMeasurementAPI.Call.
|
||||
type SubmitMeasurementCaller interface {
|
||||
// callerForSubmitMeasurementAPI represents any type exposing a method
|
||||
// like simpleSubmitMeasurementAPI.Call.
|
||||
type callerForSubmitMeasurementAPI interface {
|
||||
Call(ctx context.Context, req *apimodel.SubmitMeasurementRequest) (*apimodel.SubmitMeasurementResponse, error)
|
||||
}
|
||||
|
|
13
internal/engine/ooapi/client.go
Normal file
13
internal/engine/ooapi/client.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package ooapi
|
||||
|
||||
// Client is a client for speaking with the OONI API. Make sure you
|
||||
// fill in the mandatory fields.
|
||||
type Client struct {
|
||||
BaseURL string // optional
|
||||
GobCodec GobCodec // optional
|
||||
HTTPClient HTTPClient // optional
|
||||
JSONCodec JSONCodec // optional
|
||||
KVStore KVStore // mandatory
|
||||
RequestMaker RequestMaker // optional
|
||||
UserAgent string // optional
|
||||
}
|
214
internal/engine/ooapi/clientcall.go
Normal file
214
internal/engine/ooapi/clientcall.go
Normal file
|
@ -0,0 +1,214 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-03-31 16:50:05.02947443 +0200 CEST m=+0.000143471
|
||||
|
||||
package ooapi
|
||||
|
||||
//go:generate go run ./internal/generator -file clientcall.go
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
func (c *Client) newCheckReportIDCaller() callerForCheckReportIDAPI {
|
||||
return &simpleCheckReportIDAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckReportID calls the CheckReportID API.
|
||||
func (c *Client) CheckReportID(
|
||||
ctx context.Context, req *apimodel.CheckReportIDRequest,
|
||||
) (*apimodel.CheckReportIDResponse, error) {
|
||||
api := c.newCheckReportIDCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newCheckInCaller() callerForCheckInAPI {
|
||||
return &simpleCheckInAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// CheckIn calls the CheckIn API.
|
||||
func (c *Client) CheckIn(
|
||||
ctx context.Context, req *apimodel.CheckInRequest,
|
||||
) (*apimodel.CheckInResponse, error) {
|
||||
api := c.newCheckInCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newMeasurementMetaCaller() callerForMeasurementMetaAPI {
|
||||
return &withCacheMeasurementMetaAPI{
|
||||
API: &simpleMeasurementMetaAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
GobCodec: c.GobCodec,
|
||||
KVStore: c.KVStore,
|
||||
}
|
||||
}
|
||||
|
||||
// MeasurementMeta calls the MeasurementMeta API.
|
||||
func (c *Client) MeasurementMeta(
|
||||
ctx context.Context, req *apimodel.MeasurementMetaRequest,
|
||||
) (*apimodel.MeasurementMetaResponse, error) {
|
||||
api := c.newMeasurementMetaCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newTestHelpersCaller() callerForTestHelpersAPI {
|
||||
return &simpleTestHelpersAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelpers calls the TestHelpers API.
|
||||
func (c *Client) TestHelpers(
|
||||
ctx context.Context, req *apimodel.TestHelpersRequest,
|
||||
) (apimodel.TestHelpersResponse, error) {
|
||||
api := c.newTestHelpersCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newPsiphonConfigCaller() callerForPsiphonConfigAPI {
|
||||
return &withLoginPsiphonConfigAPI{
|
||||
API: &simplePsiphonConfigAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
JSONCodec: c.JSONCodec,
|
||||
KVStore: c.KVStore,
|
||||
RegisterAPI: &simpleRegisterAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
LoginAPI: &simpleLoginAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PsiphonConfig calls the PsiphonConfig API.
|
||||
func (c *Client) PsiphonConfig(
|
||||
ctx context.Context, req *apimodel.PsiphonConfigRequest,
|
||||
) (apimodel.PsiphonConfigResponse, error) {
|
||||
api := c.newPsiphonConfigCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newTorTargetsCaller() callerForTorTargetsAPI {
|
||||
return &withLoginTorTargetsAPI{
|
||||
API: &simpleTorTargetsAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
JSONCodec: c.JSONCodec,
|
||||
KVStore: c.KVStore,
|
||||
RegisterAPI: &simpleRegisterAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
LoginAPI: &simpleLoginAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TorTargets calls the TorTargets API.
|
||||
func (c *Client) TorTargets(
|
||||
ctx context.Context, req *apimodel.TorTargetsRequest,
|
||||
) (apimodel.TorTargetsResponse, error) {
|
||||
api := c.newTorTargetsCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newURLsCaller() callerForURLsAPI {
|
||||
return &simpleURLsAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// URLs calls the URLs API.
|
||||
func (c *Client) URLs(
|
||||
ctx context.Context, req *apimodel.URLsRequest,
|
||||
) (*apimodel.URLsResponse, error) {
|
||||
api := c.newURLsCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newOpenReportCaller() callerForOpenReportAPI {
|
||||
return &simpleOpenReportAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// OpenReport calls the OpenReport API.
|
||||
func (c *Client) OpenReport(
|
||||
ctx context.Context, req *apimodel.OpenReportRequest,
|
||||
) (*apimodel.OpenReportResponse, error) {
|
||||
api := c.newOpenReportCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
||||
|
||||
func (c *Client) newSubmitMeasurementCaller() callerForSubmitMeasurementAPI {
|
||||
return &simpleSubmitMeasurementAPI{
|
||||
BaseURL: c.BaseURL,
|
||||
HTTPClient: c.HTTPClient,
|
||||
JSONCodec: c.JSONCodec,
|
||||
RequestMaker: c.RequestMaker,
|
||||
UserAgent: c.UserAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitMeasurement calls the SubmitMeasurement API.
|
||||
func (c *Client) SubmitMeasurement(
|
||||
ctx context.Context, req *apimodel.SubmitMeasurementRequest,
|
||||
) (*apimodel.SubmitMeasurementResponse, error) {
|
||||
api := c.newSubmitMeasurementCaller()
|
||||
return api.Call(ctx, req)
|
||||
}
|
898
internal/engine/ooapi/clientcall_test.go
Normal file
898
internal/engine/ooapi/clientcall_test.go
Normal file
|
@ -0,0 +1,898 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-03-31 16:50:05.248051533 +0200 CEST m=+0.000086484
|
||||
|
||||
package ooapi
|
||||
|
||||
//go:generate go run ./internal/generator -file clientcall_test.go
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/ooni/probe-cli/v3/internal/engine/ooapi/apimodel"
|
||||
)
|
||||
|
||||
type handleClientCallCheckReportID struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.CheckReportIDResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallCheckReportID) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.CheckReportIDResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestCheckReportIDClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallCheckReportID{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.CheckReportIDRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.CheckReportID(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simpleCheckReportIDAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallCheckIn struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.CheckInResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallCheckIn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.CheckInResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestCheckInClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallCheckIn{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.CheckInRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.CheckIn(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "POST" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the body
|
||||
if handler.contentType != "application/json" {
|
||||
t.Fatal("invalid content-type header")
|
||||
}
|
||||
got := &apimodel.CheckInRequest{}
|
||||
if err := json.Unmarshal(handler.body, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(req, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallMeasurementMeta struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.MeasurementMetaResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallMeasurementMeta) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.MeasurementMetaResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestMeasurementMetaClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallMeasurementMeta{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.MeasurementMetaRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.MeasurementMeta(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simpleMeasurementMetaAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallTestHelpers struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp apimodel.TestHelpersResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallTestHelpers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out apimodel.TestHelpersResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestTestHelpersClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallTestHelpers{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.TestHelpersRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.TestHelpers(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simpleTestHelpersAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallPsiphonConfig struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp apimodel.PsiphonConfigResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallPsiphonConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
if r.URL.Path == "/api/v1/register" {
|
||||
var out apimodel.RegisterResponse
|
||||
ff.fill(&out)
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/v1/login" {
|
||||
var out apimodel.LoginResponse
|
||||
ff.fill(&out)
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out apimodel.PsiphonConfigResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestPsiphonConfigClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallPsiphonConfig{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.PsiphonConfigRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.PsiphonConfig(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simplePsiphonConfigAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallTorTargets struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp apimodel.TorTargetsResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallTorTargets) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
if r.URL.Path == "/api/v1/register" {
|
||||
var out apimodel.RegisterResponse
|
||||
ff.fill(&out)
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/v1/login" {
|
||||
var out apimodel.LoginResponse
|
||||
ff.fill(&out)
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out apimodel.TorTargetsResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestTorTargetsClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallTorTargets{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.TorTargetsRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.TorTargets(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simpleTorTargetsAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallURLs struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.URLsResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallURLs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.URLsResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestURLsClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallURLs{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.URLsRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.URLs(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "GET" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the query
|
||||
api := &simpleURLsAPI{BaseURL: srvr.URL}
|
||||
httpReq, err := api.newRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.Path, httpReq.URL.Path); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
if diff := cmp.Diff(handler.url.RawQuery, httpReq.URL.RawQuery); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallOpenReport struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.OpenReportResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallOpenReport) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.OpenReportResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestOpenReportClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallOpenReport{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.OpenReportRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.OpenReport(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "POST" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the body
|
||||
if handler.contentType != "application/json" {
|
||||
t.Fatal("invalid content-type header")
|
||||
}
|
||||
got := &apimodel.OpenReportRequest{}
|
||||
if err := json.Unmarshal(handler.body, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(req, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
type handleClientCallSubmitMeasurement struct {
|
||||
accept string
|
||||
body []byte
|
||||
contentType string
|
||||
count int32
|
||||
method string
|
||||
mu sync.Mutex
|
||||
resp *apimodel.SubmitMeasurementResponse
|
||||
url *url.URL
|
||||
userAgent string
|
||||
}
|
||||
|
||||
func (h *handleClientCallSubmitMeasurement) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ff := fakeFill{}
|
||||
defer h.mu.Unlock()
|
||||
h.mu.Lock()
|
||||
if h.count > 0 {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.count++
|
||||
if r.Body != nil {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
h.body = data
|
||||
}
|
||||
h.method = r.Method
|
||||
h.url = r.URL
|
||||
h.accept = r.Header.Get("Accept")
|
||||
h.contentType = r.Header.Get("Content-Type")
|
||||
h.userAgent = r.Header.Get("User-Agent")
|
||||
var out *apimodel.SubmitMeasurementResponse
|
||||
ff.fill(&out)
|
||||
h.resp = out
|
||||
data, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func TestSubmitMeasurementClientCallRoundTrip(t *testing.T) {
|
||||
// setup
|
||||
handler := &handleClientCallSubmitMeasurement{}
|
||||
srvr := httptest.NewServer(handler)
|
||||
defer srvr.Close()
|
||||
req := &apimodel.SubmitMeasurementRequest{}
|
||||
ff := &fakeFill{}
|
||||
ff.fill(&req)
|
||||
clnt := &Client{KVStore: &MemKVStore{}, BaseURL: srvr.URL}
|
||||
ff.fill(&clnt.UserAgent)
|
||||
// issue request
|
||||
ctx := context.Background()
|
||||
resp, err := clnt.SubmitMeasurement(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response here")
|
||||
}
|
||||
// compare our response and server's one
|
||||
if diff := cmp.Diff(handler.resp, resp); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
// check whether headers are OK
|
||||
if handler.accept != "application/json" {
|
||||
t.Fatal("invalid accept header")
|
||||
}
|
||||
if handler.userAgent != clnt.UserAgent {
|
||||
t.Fatal("invalid user-agent header")
|
||||
}
|
||||
// check whether the method is OK
|
||||
if handler.method != "POST" {
|
||||
t.Fatal("invalid method")
|
||||
}
|
||||
// check the body
|
||||
if handler.contentType != "application/json" {
|
||||
t.Fatal("invalid content-type header")
|
||||
}
|
||||
got := &apimodel.SubmitMeasurementRequest{}
|
||||
if err := json.Unmarshal(handler.body, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if diff := cmp.Diff(req, got); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:52.108352268 +0100 CET m=+0.000275862
|
||||
// 2021-03-31 16:50:05.487854795 +0200 CEST m=+0.000140777
|
||||
|
||||
package ooapi
|
||||
|
||||
//go:generate go run ./internal/generator -file cloners.go
|
||||
|
||||
// PsiphonConfigCaller represents any type exposing a method
|
||||
// like PsiphonConfigAPI.WithToken.
|
||||
type PsiphonConfigCloner interface {
|
||||
WithToken(token string) PsiphonConfigCaller
|
||||
// clonerForPsiphonConfigAPI represents any type exposing a method
|
||||
// like simplePsiphonConfigAPI.WithToken.
|
||||
type clonerForPsiphonConfigAPI interface {
|
||||
WithToken(token string) callerForPsiphonConfigAPI
|
||||
}
|
||||
|
||||
// TorTargetsCaller represents any type exposing a method
|
||||
// like TorTargetsAPI.WithToken.
|
||||
type TorTargetsCloner interface {
|
||||
WithToken(token string) TorTargetsCaller
|
||||
// clonerForTorTargetsAPI represents any type exposing a method
|
||||
// like simpleTorTargetsAPI.WithToken.
|
||||
type clonerForTorTargetsAPI interface {
|
||||
WithToken(token string) callerForTorTargetsAPI
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ type RequestMaker interface {
|
|||
NewRequest(ctx context.Context, method, URL string, body io.Reader) (*http.Request, error)
|
||||
}
|
||||
|
||||
// TemplateExecutor parses and executes a text template.
|
||||
type TemplateExecutor interface {
|
||||
// templateExecutor parses and executes a text template.
|
||||
type templateExecutor interface {
|
||||
// Execute takes in input a template string and some piece of data. It
|
||||
// returns either a string where template parameters have been replaced,
|
||||
// on success, or an error, on failure.
|
||||
|
|
|
@ -1,108 +1,19 @@
|
|||
// Package ooapi contains clients for the OONI API. We
|
||||
// automatically generate the code in this package from
|
||||
// the apimodel and internal/generator packages. For
|
||||
// each OONI API, we define up to three data structures:
|
||||
// Package ooapi contains a client for the OONI API. We
|
||||
// automatically generate the code in this package from the
|
||||
// apimodel and internal/generator packages.
|
||||
//
|
||||
// 1. a data structure representing the API;
|
||||
// Usage
|
||||
//
|
||||
// 2. a caching data structure, if the API
|
||||
// supports caching;
|
||||
// You need to create a Client. Make sure you set all
|
||||
// the mandatory fields. You will then have a function
|
||||
// for every supported OONI API. This function will
|
||||
// take in input a context and a request. You need to
|
||||
// fill the request, of course. The return value is
|
||||
// either a response or an error.
|
||||
//
|
||||
// 3. an auto-login data structure, if the API
|
||||
// requires login.
|
||||
//
|
||||
// The rest of this documentation page describes these
|
||||
// three data structures and the design and architecture
|
||||
// of this package. Refer to subpackages for more
|
||||
// information on how to specify an API.
|
||||
//
|
||||
// API data structure
|
||||
//
|
||||
// For each API, this package defines a data structure
|
||||
// representing the API. For example, for the TorTargets API,
|
||||
// we define the TorTargetsAPI data structure.
|
||||
//
|
||||
// The API data structure defines a method named Call that
|
||||
// allows calling the specified API. Call takes as arguments
|
||||
// a context and the request for the API and returns the
|
||||
// API response or an error.
|
||||
//
|
||||
// Request and response messages live inside the apimodel
|
||||
// subpackage. We name them after the API. Thus, for
|
||||
// the TorTargets API, the request is TorTargetsRequest,
|
||||
// and the response is TorTargetsResponse.
|
||||
//
|
||||
// API data structures are cheap to create and do not
|
||||
// mutate. They should be used in place and then forgotten
|
||||
// off once the API call is complete.
|
||||
//
|
||||
// Unless explicitly indicated, the zero value of every
|
||||
// API data structure is a valid API data structure.
|
||||
//
|
||||
// In terms of dependencies, APIs certainly need an http.Client
|
||||
// to communicate with the OONI backend. To represent such a
|
||||
// client, we use the HTTPClient interface. If you do not tell
|
||||
// an API which http.Client to use, we will default to the
|
||||
// standard library's http.DefaultClient.
|
||||
//
|
||||
// An API also depends on a JSONCodec. That is, on a data
|
||||
// structures that encodes data to/from JSON. If you do not
|
||||
// specify explicitly a JSONCodec, we will use the Go
|
||||
// standard library's JSON implementation.
|
||||
//
|
||||
// When an API requires authentication, you need to tell
|
||||
// it which authentication token to use. This gives you
|
||||
// control over obtaining the token and is the low-level
|
||||
// way of interacting with authenticated APIs. We recommend
|
||||
// using the auto-login wrappers instead (see below).
|
||||
//
|
||||
// Authenticated APIs also define the WithToken method. This
|
||||
// method takes as argument a token and returns a copy of the
|
||||
// original API using the given token. We use this method
|
||||
// to implement auto-login wrappers.
|
||||
//
|
||||
// For each API, we also define two interfaces:
|
||||
//
|
||||
// 1. the Caller interface represents the possibility of
|
||||
// calling a specific API with the correct arguments;
|
||||
//
|
||||
// 2. the Cloner interface represents the possibility of
|
||||
// calling WithToken on the given API.
|
||||
//
|
||||
// They abstract the interaction between the API type and
|
||||
// its caching and auto-login wrappers.
|
||||
//
|
||||
// Caching
|
||||
//
|
||||
// If an API supports caching, we define a type whose name
|
||||
// ends in Cache. The TorTargets API cache, for example,
|
||||
// is TorTargetsCache. These caching types wrap the API type
|
||||
// and provide the caching functionality.
|
||||
//
|
||||
// Because the cache needs to read from and write to the
|
||||
// disk, a caching type needs a KVStore. A KVStore is
|
||||
// an interface that allow you to bind a specific key to
|
||||
// a given blob of bytes and to retrieve such bytes later.
|
||||
//
|
||||
// Caches use the gob data format from the Go standard
|
||||
// library (`encoding/gob`). We abstract this dependency
|
||||
// using the GobCodec interface. By default, when you
|
||||
// do not specify a GobCodec we use the implementation
|
||||
// of gob from the Go standard library.
|
||||
//
|
||||
// See the example describing caching for more information
|
||||
// on how to use caching.
|
||||
//
|
||||
// Auto-login
|
||||
//
|
||||
// If an API supports auto-login, we define a type whose
|
||||
// name ends with WithLogin. The TorTargets auto-login struct,
|
||||
// for example, is called TorTargetsAPIWithLogin.
|
||||
//
|
||||
// Auto-login wrappers need to store persistent data. We
|
||||
// use a KVStore for that (see above). We encode login data
|
||||
// using JSON. To this end, we use a JSONCodec (also
|
||||
// described above).
|
||||
// If an API requires login, we will automatically
|
||||
// perform the login. If an API uses caching, we will
|
||||
// automatically use the cache.
|
||||
//
|
||||
// See the example describing auto-login for more information
|
||||
// on how to use auto-login.
|
||||
|
@ -142,22 +53,4 @@
|
|||
// The ./internal/generator contains code to generate most
|
||||
// code in this package. In particular, the spec.go file is
|
||||
// the specification of the APIs.
|
||||
//
|
||||
// Notable generated files
|
||||
//
|
||||
// - apis.go: contains APIs (e.g., TorTargetsAPI);
|
||||
//
|
||||
// - caching.go: contains caching wrappers for every API
|
||||
// that declares that it needs a cache (e.g., TorTargetsCache);
|
||||
//
|
||||
// - callers.go: contains Callers;
|
||||
//
|
||||
// - cloners.go: contains the Cloners;
|
||||
//
|
||||
// - login.go: contains auto-login wrappers (e.g.,
|
||||
// TorTargetsAPIWithLogin);
|
||||
//
|
||||
// - requests.go: contains code to generate http.Requests.
|
||||
//
|
||||
// - responses.go: code to parse http.Responses.
|
||||
package ooapi
|
||||
|
|
|
@ -2,13 +2,13 @@ package ooapi
|
|||
|
||||
import "errors"
|
||||
|
||||
// Errors defined by this package. In addition to these errors, this
|
||||
// package may of course return any other stdlib specific error.
|
||||
// Errors defined by this package.
|
||||
var (
|
||||
ErrEmptyField = errors.New("apiclient: empty field")
|
||||
ErrHTTPFailure = errors.New("apiclient: http request failed")
|
||||
ErrJSONLiteralNull = errors.New("apiclient: server returned us a literal null")
|
||||
ErrMissingToken = errors.New("apiclient: missing auth token")
|
||||
ErrUnauthorized = errors.New("apiclient: not authorized")
|
||||
errCacheNotFound = errors.New("apiclient: not found in cache")
|
||||
ErrAPICallFailed = errors.New("ooapi: API call failed")
|
||||
ErrEmptyField = errors.New("ooapi: empty field")
|
||||
ErrHTTPFailure = errors.New("ooapi: http request failed")
|
||||
ErrJSONLiteralNull = errors.New("ooapi: server returned us a literal null")
|
||||
ErrMissingToken = errors.New("ooapi: missing auth token")
|
||||
ErrUnauthorized = errors.New("ooapi: not authorized")
|
||||
errCacheNotFound = errors.New("ooapi: not found in cache")
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by go generate; DO NOT EDIT.
|
||||
// 2021-02-26 15:45:52.357709034 +0100 CET m=+0.000208565
|
||||
// 2021-03-31 16:50:05.702711633 +0200 CEST m=+0.000209246
|
||||
|
||||
package ooapi
|
||||
|
||||
|
@ -24,7 +24,7 @@ func (fapi *FakeCheckReportIDAPI) Call(ctx context.Context, req *apimodel.CheckR
|
|||
}
|
||||
|
||||
var (
|
||||
_ CheckReportIDCaller = &FakeCheckReportIDAPI{}
|
||||
_ callerForCheckReportIDAPI = &FakeCheckReportIDAPI{}
|
||||
)
|
||||
|
||||
type FakeCheckInAPI struct {
|
||||
|
@ -39,7 +39,7 @@ func (fapi *FakeCheckInAPI) Call(ctx context.Context, req *apimodel.CheckInReque
|
|||
}
|
||||
|
||||
var (
|
||||
_ CheckInCaller = &FakeCheckInAPI{}
|
||||
_ callerForCheckInAPI = &FakeCheckInAPI{}
|
||||
)
|
||||
|
||||
type FakeLoginAPI struct {
|
||||
|
@ -54,7 +54,7 @@ func (fapi *FakeLoginAPI) Call(ctx context.Context, req *apimodel.LoginRequest)
|
|||
}
|
||||
|
||||
var (
|
||||
_ LoginCaller = &FakeLoginAPI{}
|
||||
_ callerForLoginAPI = &FakeLoginAPI{}
|
||||
)
|
||||
|
||||
type FakeMeasurementMetaAPI struct {
|
||||
|
@ -69,7 +69,7 @@ func (fapi *FakeMeasurementMetaAPI) Call(ctx context.Context, req *apimodel.Meas
|
|||
}
|
||||
|
||||
var (
|
||||
_ MeasurementMetaCaller = &FakeMeasurementMetaAPI{}
|
||||
_ callerForMeasurementMetaAPI = &FakeMeasurementMetaAPI{}
|
||||
)
|
||||
|
||||
type FakeRegisterAPI struct {
|
||||
|
@ -84,7 +84,7 @@ func (fapi *FakeRegisterAPI) Call(ctx context.Context, req *apimodel.RegisterReq
|
|||
}
|
||||
|
||||
var (
|
||||
_ RegisterCaller = &FakeRegisterAPI{}
|
||||
_ callerForRegisterAPI = &FakeRegisterAPI{}
|
||||
)
|
||||
|
||||
type FakeTestHelpersAPI struct {
|
||||
|
@ -99,11 +99,11 @@ func (fapi *FakeTestHelpersAPI) Call(ctx context.Context, req *apimodel.TestHelp
|
|||
}
|
||||
|
||||
var (
|
||||
_ TestHelpersCaller = &FakeTestHelpersAPI{}
|
||||
_ callerForTestHelpersAPI = &FakeTestHelpersAPI{}
|
||||
)
|
||||
|
||||
type FakePsiphonConfigAPI struct {
|
||||
WithResult PsiphonConfigCaller
|
||||
WithResult callerForPsiphonConfigAPI
|
||||
Err error
|
||||
Response apimodel.PsiphonConfigResponse
|
||||
CountCall int32
|
||||
|
@ -114,17 +114,17 @@ func (fapi *FakePsiphonConfigAPI) Call(ctx context.Context, req *apimodel.Psipho
|
|||
return fapi.Response, fapi.Err
|
||||
}
|
||||
|
||||
func (fapi *FakePsiphonConfigAPI) WithToken(token string) PsiphonConfigCaller {
|
||||
func (fapi *FakePsiphonConfigAPI) WithToken(token string) callerForPsiphonConfigAPI {
|
||||
return fapi.WithResult
|
||||
}
|
||||
|
||||
var (
|
||||
_ PsiphonConfigCaller = &FakePsiphonConfigAPI{}
|
||||
_ PsiphonConfigCloner = &FakePsiphonConfigAPI{}
|
||||
_ callerForPsiphonConfigAPI = &FakePsiphonConfigAPI{}
|
||||
_ clonerForPsiphonConfigAPI = &FakePsiphonConfigAPI{}
|
||||
)
|
||||
|
||||
type FakeTorTargetsAPI struct {
|
||||
WithResult TorTargetsCaller
|
||||
WithResult callerForTorTargetsAPI
|
||||
Err error
|
||||
Response apimodel.TorTargetsResponse
|
||||
CountCall int32
|
||||
|
@ -135,13 +135,13 @@ func (fapi *FakeTorTargetsAPI) Call(ctx context.Context, req *apimodel.TorTarget
|
|||
return fapi.Response, fapi.Err
|
||||
}
|
||||
|
||||
func (fapi *FakeTorTargetsAPI) WithToken(token string) TorTargetsCaller {
|
||||
func (fapi *FakeTorTargetsAPI) WithToken(token string) callerForTorTargetsAPI {
|
||||
return fapi.WithResult
|
||||
}
|
||||
|
||||
var (
|
||||
_ TorTargetsCaller = &FakeTorTargetsAPI{}
|
||||
_ TorTargetsCloner = &FakeTorTargetsAPI{}
|
||||
_ callerForTorTargetsAPI = &FakeTorTargetsAPI{}
|
||||
_ clonerForTorTargetsAPI = &FakeTorTargetsAPI{}
|
||||
)
|
||||
|
||||
type FakeURLsAPI struct {
|
||||
|
@ -156,7 +156,7 @@ func (fapi *FakeURLsAPI) Call(ctx context.Context, req *apimodel.URLsRequest) (*
|
|||
}
|
||||
|
||||
var (
|
||||
_ URLsCaller = &FakeURLsAPI{}
|
||||
_ callerForURLsAPI = &FakeURLsAPI{}
|
||||
)
|
||||
|
||||
type FakeOpenReportAPI struct {
|
||||
|
@ -171,7 +171,7 @@ func (fapi *FakeOpenReportAPI) Call(ctx context.Context, req *apimodel.OpenRepor
|
|||
}
|
||||
|
||||
var (
|
||||
_ OpenReportCaller = &FakeOpenReportAPI{}
|
||||
_ callerForOpenReportAPI = &FakeOpenReportAPI{}
|
||||
)
|
||||
|
||||
type FakeSubmitMeasurementAPI struct {
|
||||
|
@ -186,5 +186,5 @@ func (fapi *FakeSubmitMeasurementAPI) Call(ctx context.Context, req *apimodel.Su
|
|||
}
|
||||
|
||||
var (
|
||||
_ SubmitMeasurementCaller = &FakeSubmitMeasurementAPI{}
|
||||
_ callerForSubmitMeasurementAPI = &FakeSubmitMeasurementAPI{}
|
||||
)
|
||||
|
|
21
internal/engine/ooapi/httpclient_test.go
Normal file
21
internal/engine/ooapi/httpclient_test.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package ooapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type VerboseHTTPClient struct {
|
||||
T *testing.T
|
||||
}
|
||||
|
||||
func (c *VerboseHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.T.Logf("> %s %s", req.Method, req.URL.String())
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
c.T.Logf("< %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
c.T.Logf("< %d", resp.StatusCode)
|
||||
return resp, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user