ooni-probe-cli/pkg/oonimkall/session_integration_test.go
Lorenzo Primiterra 772de83a06
oonimkall: add wrappers for /test-list/urls (#221)
* First wrapper implementation

* Adding CountryCode parameter

* requested changes

* Update pkg/oonimkall/session.go

Co-authored-by: Simone Basso <bassosimone@gmail.com>

* Requested changes

* Update pkg/oonimkall/session_integration_test.go

Co-authored-by: Simone Basso <bassosimone@gmail.com>

* Update pkg/oonimkall/session_integration_test.go

Co-authored-by: Simone Basso <bassosimone@gmail.com>

* Remove duplicated code

* Apply suggestions from code review

Co-authored-by: Simone Basso <bassosimone@gmail.com>
2021-02-26 10:00:25 +01:00

495 lines
12 KiB
Go

package oonimkall_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"runtime"
"strings"
"testing"
"time"
engine "github.com/ooni/probe-cli/v3/internal/engine"
"github.com/ooni/probe-cli/v3/internal/engine/geolocate"
"github.com/ooni/probe-cli/v3/internal/engine/model"
"github.com/ooni/probe-cli/v3/pkg/oonimkall"
)
func NewSessionWithAssetsDir(assetsDir string) (*oonimkall.Session, error) {
return oonimkall.NewSession(&oonimkall.SessionConfig{
AssetsDir: assetsDir,
ProbeServicesURL: "https://ams-pg-test.ooni.org/",
SoftwareName: "oonimkall-test",
SoftwareVersion: "0.1.0",
StateDir: "../testdata/oonimkall/state",
TempDir: "../testdata/",
})
}
func NewSession() (*oonimkall.Session, error) {
return NewSessionWithAssetsDir("../testdata/oonimkall/assets")
}
func TestNewSessionWithInvalidStateDir(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := oonimkall.NewSession(&oonimkall.SessionConfig{
StateDir: "",
})
if err == nil || !strings.HasSuffix(err.Error(), "no such file or directory") {
t.Fatal("not the error we expected")
}
if sess != nil {
t.Fatal("expected a nil Session here")
}
}
func TestNewSessionWithMissingSoftwareName(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := oonimkall.NewSession(&oonimkall.SessionConfig{
StateDir: "../testdata/oonimkall/state",
})
if err == nil || err.Error() != "AssetsDir is empty" {
t.Fatal("not the error we expected")
}
if sess != nil {
t.Fatal("expected a nil Session here")
}
}
func TestMaybeUpdateResourcesWithCancelledContext(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
dir, err := ioutil.TempDir("", "xx")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
sess, err := NewSessionWithAssetsDir(dir)
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
ctx.Cancel() // cause immediate failure
err = sess.MaybeUpdateResources(ctx)
if !errors.Is(err, context.Canceled) {
t.Fatalf("not the error we expected: %+v", err)
}
}
func ReduceErrorForGeolocate(err error) error {
if err == nil {
return errors.New("we expected an error here")
}
if errors.Is(err, context.Canceled) {
return nil // when we have not downloaded the resources yet
}
if !errors.Is(err, geolocate.ErrAllIPLookuppersFailed) {
return nil // otherwise
}
return fmt.Errorf("not the error we expected: %w", err)
}
func TestGeolocateWithCancelledContext(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
ctx.Cancel() // cause immediate failure
location, err := sess.Geolocate(ctx)
if err := ReduceErrorForGeolocate(err); err != nil {
t.Fatal(err)
}
if location != nil {
t.Fatal("expected nil location here")
}
}
func TestGeolocateGood(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
location, err := sess.Geolocate(ctx)
if err != nil {
t.Fatal(err)
}
if location.ASN == "" {
t.Fatal("location.ASN is empty")
}
if location.Country == "" {
t.Fatal("location.Country is empty")
}
if location.IP == "" {
t.Fatal("location.IP is empty")
}
if location.Org == "" {
t.Fatal("location.Org is empty")
}
}
func ReduceErrorForSubmitter(err error) error {
if err == nil {
return errors.New("we expected an error here")
}
if errors.Is(err, context.Canceled) {
return nil // when we have not downloaded the resources yet
}
if err.Error() == "all available probe services failed" {
return nil // otherwise
}
return fmt.Errorf("not the error we expected: %w", err)
}
func TestSubmitWithCancelledContext(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
ctx.Cancel() // cause immediate failure
result, err := sess.Submit(ctx, "{}")
if err := ReduceErrorForSubmitter(err); err != nil {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}
func TestSubmitWithInvalidJSON(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
result, err := sess.Submit(ctx, "{")
if err == nil || err.Error() != "unexpected end of JSON input" {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}
func DoSubmission(ctx *oonimkall.Context, sess *oonimkall.Session) error {
inputm := model.Measurement{
DataFormatVersion: "0.2.0",
MeasurementStartTime: "2019-10-28 12:51:07",
MeasurementRuntime: 1.71,
ProbeASN: "AS30722",
ProbeCC: "IT",
ProbeIP: "127.0.0.1",
ReportID: "",
ResolverIP: "172.217.33.129",
SoftwareName: "miniooni",
SoftwareVersion: "0.1.0-dev",
TestKeys: map[string]bool{"success": true},
TestName: "example",
TestVersion: "0.1.0",
}
inputd, err := json.Marshal(inputm)
if err != nil {
return err
}
result, err := sess.Submit(ctx, string(inputd))
if err != nil {
return fmt.Errorf("session_test.go: submit failed: %w", err)
}
if result.UpdatedMeasurement == "" {
return errors.New("expected non empty measurement")
}
if result.UpdatedReportID == "" {
return errors.New("expected non empty report ID")
}
var outputm model.Measurement
return json.Unmarshal([]byte(result.UpdatedMeasurement), &outputm)
}
func TestSubmitMeasurementGood(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
if err := DoSubmission(ctx, sess); err != nil {
t.Fatal(err)
}
}
func TestSubmitCancelContextAfterFirstSubmission(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
if err := DoSubmission(ctx, sess); err != nil {
t.Fatal(err)
}
ctx.Cancel() // fail second submission
err = DoSubmission(ctx, sess)
if err == nil || !strings.HasPrefix(err.Error(), "session_test.go: submit failed") {
t.Fatalf("not the error we expected: %+v", err)
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("not the error we expected: %+v", err)
}
}
func TestCheckInSuccess(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if err != nil {
t.Fatalf("unexpected error: %+v", err)
}
if result == nil || result.WebConnectivity == nil {
t.Fatal("got nil result or WebConnectivity")
}
if len(result.WebConnectivity.URLs) < 1 {
t.Fatal("unexpected number of URLs")
}
if result.WebConnectivity.ReportID == "" {
t.Fatal("got empty report ID")
}
siz := result.WebConnectivity.Size()
if siz <= 0 {
t.Fatal("unexpected number of URLs")
}
for idx := int64(0); idx < siz; idx++ {
entry := result.WebConnectivity.At(idx)
if entry.CategoryCode != "NEWS" && entry.CategoryCode != "CULTR" {
t.Fatalf("unexpected category code: %+v", entry)
}
}
if result.WebConnectivity.At(-1) != nil {
t.Fatal("expected nil here")
}
if result.WebConnectivity.At(siz) != nil {
t.Fatal("expected nil here")
}
}
func TestCheckInLookupLocationFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
ctx.Cancel() // immediate failure
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, geolocate.ErrAllIPLookuppersFailed) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}
func TestCheckInNewProbeServicesFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
sess.TestingCheckInBeforeNewProbeServicesClient = func(ctx *oonimkall.Context) {
ctx.Cancel() // cancel execution
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, engine.ErrAllProbeServicesFailed) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}
func TestCheckInCheckInFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
sess.TestingCheckInBeforeCheckIn = func(ctx *oonimkall.Context) {
ctx.Cancel() // cancel execution
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, context.Canceled) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}
func TestCheckInNoParams(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
}
result, err := sess.CheckIn(ctx, &config)
if err == nil || err.Error() != "oonimkall: missing webconnectivity config" {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("unexpected not nil result here")
}
}
func TestFetchURLListSuccess(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.URLListConfig{
Limit: 10,
}
config.AddCategory("NEWS")
config.AddCategory("CULTR")
result, err := sess.FetchURLList(ctx, &config)
if err != nil {
t.Fatalf("unexpected error: %+v", err)
}
if result == nil || result.Results == nil {
t.Fatal("got nil result")
}
for _, entry := range result.Results {
if entry.CategoryCode != "NEWS" && entry.CategoryCode != "CULTR" {
t.Fatalf("unexpected category code: %+v", entry)
}
}
}
func TestFetchURLListWithCC(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.URLListConfig{
CountryCode: "IT",
}
config.AddCategory("NEWS")
config.AddCategory("CULTR")
result, err := sess.FetchURLList(ctx, &config)
if err != nil {
t.Fatalf("unexpected error: %+v", err)
}
if result == nil || result.Results == nil {
t.Fatal("got nil result")
}
found := false
for _, entry := range result.Results {
if entry.CountryCode == "IT" {
found = true
}
}
if !found {
t.Fatalf("not found url for country code: IT")
}
}
func TestMain(m *testing.M) {
// Here we're basically testing whether eventually the finalizers
// will run and the number of active sessions and cancels will become
// balanced. Especially for the number of active cancels, this is an
// indication that we've correctly cleaned them up in the session.
if exitcode := m.Run(); exitcode != 0 {
os.Exit(exitcode)
}
for {
runtime.GC()
m, n := oonimkall.ActiveContexts.Load(), oonimkall.ActiveSessions.Load()
fmt.Printf("./oonimkall: ActiveContexts: %d; ActiveSessions: %d\n", m, n)
if m == 0 && n == 0 {
break
}
time.Sleep(1 * time.Second)
}
os.Exit(0)
}