From b5b5bd96dfb79778c8444a40d99d13ff207426c0 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 25 Mar 2020 16:58:15 -0700 Subject: [PATCH] client: Move test setup into unit_test.go Copy weldrcheck's utils.go into client, switch to using TestState struct to hold global test data. Only build unit_test.go if integration has not been selected. This is in preparation to moving weldrcheck code into client *_test.go files so that the test code can be shared and run against a mock server during unit testing, or against a running WELDR API server during integration testing. --- internal/client/client_test.go | 86 ++++------------------------------ internal/client/unit_test.go | 68 +++++++++++++++++++++++++++ internal/client/utils.go | 63 +++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 76 deletions(-) create mode 100644 internal/client/unit_test.go create mode 100644 internal/client/utils.go diff --git a/internal/client/client_test.go b/internal/client/client_test.go index 0358cb64d..af7a1aafd 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -3,33 +3,16 @@ package client import ( - "context" - "io/ioutil" - "log" - "net" "net/http" - "os" "strings" "testing" - "time" - - "github.com/osbuild/osbuild-composer/internal/distro/fedoratest" - rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd" - "github.com/osbuild/osbuild-composer/internal/rpmmd" - "github.com/osbuild/osbuild-composer/internal/weldr" "github.com/google/go-cmp/cmp" ) -// socketPath is the path to the temporary unix domain socket -var socketPath string - -// socket is the client connection to the server on socketPath -var socket *http.Client - func TestRequest(t *testing.T) { // Make a request to the status route - resp, err := Request(socket, "GET", "/api/status", "", map[string]string{}) + resp, err := Request(testState.socket, "GET", "/api/status", "", map[string]string{}) if err != nil { t.Fatalf("Request good route failed: %v", err) } @@ -38,7 +21,7 @@ func TestRequest(t *testing.T) { } // Make a request to a bad route - resp, err = Request(socket, "GET", "/invalidroute", "", map[string]string{}) + resp, err = Request(testState.socket, "GET", "/invalidroute", "", map[string]string{}) if err != nil { t.Fatalf("Request bad route failed: %v", err) } @@ -53,7 +36,7 @@ func TestRequest(t *testing.T) { } // Make a request with a bad offset to trigger a JSON response with Status set to 400 - resp, err = Request(socket, "GET", "/api/v0/blueprints/list?offset=bad", "", map[string]string{}) + resp, err = Request(testState.socket, "GET", "/api/v0/blueprints/list?offset=bad", "", map[string]string{}) if err != nil { t.Fatalf("Request bad offset failed: %v", err) } @@ -90,7 +73,7 @@ func TestAPIResponse(t *testing.T) { func TestGetRaw(t *testing.T) { // Get raw data - b, resp, err := GetRaw(socket, "GET", "/api/status") + b, resp, err := GetRaw(testState.socket, "GET", "/api/status") if err != nil { t.Fatalf("GetRaw failed with a client error: %v", err) } @@ -101,7 +84,7 @@ func TestGetRaw(t *testing.T) { t.Fatal("GetRaw returned an empty string") } // Get an API error - b, resp, err = GetRaw(socket, "GET", "/api/v0/blueprints/list?offset=bad") + b, resp, err = GetRaw(testState.socket, "GET", "/api/v0/blueprints/list?offset=bad") if err != nil { t.Fatalf("GetRaw bad request failed with a client error: %v", err) } @@ -120,7 +103,7 @@ func TestGetRaw(t *testing.T) { func TestGetJSONAll(t *testing.T) { // Get all the projects - b, resp, err := GetJSONAll(socket, "/api/v0/projects/list") + b, resp, err := GetJSONAll(testState.socket, "/api/v0/projects/list") if err != nil { t.Fatalf("GetJSONAll failed with a client error: %v", err) } @@ -132,7 +115,7 @@ func TestGetJSONAll(t *testing.T) { } // Run it on a route that doesn't support offset/limit - b, resp, err = GetJSONAll(socket, "/api/status") + b, resp, err = GetJSONAll(testState.socket, "/api/status") if err == nil { t.Fatalf("GetJSONAll bad route failed: %v", b) } @@ -143,7 +126,7 @@ func TestGetJSONAll(t *testing.T) { func TestPostRaw(t *testing.T) { // There are no routes that accept raw POST w/o Content-Type so this ends up testing the error path - b, resp, err := PostRaw(socket, "/api/v0/blueprints/new", "nobody", nil) + b, resp, err := PostRaw(testState.socket, "/api/v0/blueprints/new", "nobody", nil) if err != nil { t.Fatalf("PostRaw bad request failed with a client error: %v", err) } @@ -164,7 +147,7 @@ func TestPostTOML(t *testing.T) { blueprint := `name = "test-blueprint" description = "TOML test blueprint" version = "0.0.1"` - b, resp, err := PostTOML(socket, "/api/v0/blueprints/new", blueprint) + b, resp, err := PostTOML(testState.socket, "/api/v0/blueprints/new", blueprint) if err != nil { t.Fatalf("PostTOML client failed: %v", err) } @@ -180,7 +163,7 @@ func TestPostJSON(t *testing.T) { blueprint := `{"name": "test-blueprint", "description": "JSON test blueprint", "version": "0.0.1"}` - b, resp, err := PostJSON(socket, "/api/v0/blueprints/new", blueprint) + b, resp, err := PostJSON(testState.socket, "/api/v0/blueprints/new", blueprint) if err != nil { t.Fatalf("PostJSON client failed: %v", err) } @@ -191,52 +174,3 @@ func TestPostJSON(t *testing.T) { t.Fatalf("PostJSON failed: %#v", string(b)) } } - -func TestMain(m *testing.M) { - // Setup the mocked server running on a temporary domain socket - tmpdir, err := ioutil.TempDir("", "client_test-") - if err != nil { - panic(err) - } - defer os.RemoveAll(tmpdir) - - socketPath = tmpdir + "/client_test.socket" - ln, err := net.Listen("unix", socketPath) - if err != nil { - panic(err) - } - - // Create a mock API server listening on the temporary socket - fixture := rpmmd_mock.BaseFixture() - rpm := rpmmd_mock.NewRPMMDMock(fixture) - distro := fedoratest.New() - arch, err := distro.GetArch("x86_64") - if err != nil { - panic(err) - } - repos := []rpmmd.RepoConfig{{Id: "test-id", BaseURL: "http://example.com/test/os/test_arch"}} - logger := log.New(os.Stdout, "", 0) - api := weldr.New(rpm, arch, distro, repos, logger, fixture.Store) - server := http.Server{Handler: api} - defer server.Close() - - go func() { - err := server.Serve(ln) - if err != nil { - panic(err) - } - }() - - // Setup client connection to socketPath - socket = &http.Client{ - Timeout: 60 * time.Second, - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - }, - }, - } - - // Run the tests - os.Exit(m.Run()) -} diff --git a/internal/client/unit_test.go b/internal/client/unit_test.go new file mode 100644 index 000000000..4d00f58bf --- /dev/null +++ b/internal/client/unit_test.go @@ -0,0 +1,68 @@ +// Package client contains functions for communicating with the API server +// Copyright (C) 2020 by Red Hat, Inc. + +// +build !integration + +package client + +import ( + "io/ioutil" + "log" + "net" + "net/http" + "os" + "testing" + "time" + + "github.com/osbuild/osbuild-composer/internal/distro/fedoratest" + rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd" + "github.com/osbuild/osbuild-composer/internal/rpmmd" + "github.com/osbuild/osbuild-composer/internal/weldr" +) + +// Hold test state to share between tests +var testState *TestState + +func TestMain(m *testing.M) { + // Setup the mocked server running on a temporary domain socket + tmpdir, err := ioutil.TempDir("", "client_test-") + if err != nil { + panic(err) + } + defer os.RemoveAll(tmpdir) + + socketPath := tmpdir + "/client_test.socket" + ln, err := net.Listen("unix", socketPath) + if err != nil { + panic(err) + } + + // Create a mock API server listening on the temporary socket + fixture := rpmmd_mock.BaseFixture() + rpm := rpmmd_mock.NewRPMMDMock(fixture) + distro := fedoratest.New() + arch, err := distro.GetArch("x86_64") + if err != nil { + panic(err) + } + repos := []rpmmd.RepoConfig{{Id: "test-id", BaseURL: "http://example.com/test/os/test_arch"}} + logger := log.New(os.Stdout, "", 0) + api := weldr.New(rpm, arch, distro, repos, logger, fixture.Store) + server := http.Server{Handler: api} + defer server.Close() + + go func() { + err := server.Serve(ln) + if err != nil { + panic(err) + } + }() + + testState, err = setUpTestState(socketPath, 60*time.Second) + if err != nil { + log.Fatalf("ERROR: Test setup failed: %s\n", err) + } + + // Run the tests + os.Exit(m.Run()) +} diff --git a/internal/client/utils.go b/internal/client/utils.go new file mode 100644 index 000000000..8fd00bb43 --- /dev/null +++ b/internal/client/utils.go @@ -0,0 +1,63 @@ +// Package weldrcheck contains functions used to run integration tests on a running API server +// Copyright (C) 2020 by Red Hat, Inc. + +// nolint: deadcode // These functions are used by the *_test.go code +package client + +import ( + "context" + "fmt" + "net" + "net/http" + "sort" + "strconv" + "time" +) + +type TestState struct { + socket *http.Client + apiVersion int + repoDir string +} + +// isStringInSlice returns true if the string is present, false if not +// slice must be sorted +// TODO decide if this belongs in a more widely useful package location +func isStringInSlice(slice []string, s string) bool { + i := sort.SearchStrings(slice, s) + if i < len(slice) && slice[i] == s { + return true + } + return false +} + +func setUpTestState(socketPath string, timeout time.Duration) (*TestState, error) { + var state TestState + state.socket = &http.Client{ + // TODO This may be too short/simple for downloading images + Timeout: timeout, + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + } + + // Make sure the server is running + status, resp, err := GetStatusV0(state.socket) + if err != nil { + return nil, fmt.Errorf("status request failed with client error: %s", err) + } + if resp != nil { + return nil, fmt.Errorf("status request failed: %v\n", resp) + } + apiVersion, e := strconv.Atoi(status.API) + if e != nil { + state.apiVersion = 0 + } else { + state.apiVersion = apiVersion + } + fmt.Printf("Running tests against %s %s server using V%d API\n\n", status.Backend, status.Build, state.apiVersion) + + return &state, nil +}