| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package botanist |
| |
| import ( |
| "context" |
| "fmt" |
| "io" |
| "net/http" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "cloud.google.com/go/storage" |
| ) |
| |
| // emptyPackageRepo is a helper that constructs an empty package repository |
| // with the expected structure. |
| func emptyPackageRepo(t *testing.T) string { |
| t.Helper() |
| localRepo := t.TempDir() |
| |
| repoPath := filepath.Join(localRepo, "repository") |
| if err := os.Mkdir(repoPath, os.ModePerm); err != nil { |
| t.Fatalf("Mkdir(%s) failed; got %s, want <nil> error", repoPath, err) |
| } |
| |
| rootJSONPath := filepath.Join(repoPath, "root.json") |
| f, err := os.Create(rootJSONPath) |
| if err != nil { |
| t.Fatalf("Create(%s) failed; got %s, want <nil> error", rootJSONPath, err) |
| } |
| defer f.Close() |
| |
| blobPath := filepath.Join(localRepo, "blobs") |
| if err := os.Mkdir(blobPath, os.ModePerm); err != nil { |
| t.Fatalf("Mkdir(%s) failed; got %s, want <nil> error", blobPath, err) |
| } |
| return localRepo |
| } |
| |
| func TestPackageServer(t *testing.T) { |
| testCases := []struct { |
| name string |
| endpoint string |
| local bool |
| wantStatusCode int |
| wantContents string |
| }{ |
| { |
| name: "local repository fetches work as expected", |
| endpoint: "/repository/targets.json", |
| local: true, |
| wantStatusCode: http.StatusOK, |
| wantContents: "{\"targets\": []}", |
| }, |
| { |
| name: "local blob fetches work as expected", |
| endpoint: "/blobs/123456", |
| local: true, |
| wantStatusCode: http.StatusOK, |
| wantContents: "desired contents", |
| }, |
| { |
| name: "remote repository fetches work as expected", |
| endpoint: "/repository/targets.json", |
| wantStatusCode: http.StatusOK, |
| wantContents: "{\"targets\": []}", |
| }, |
| { |
| name: "remote blob fetches work as expected", |
| endpoint: "/blobs/123456", |
| wantStatusCode: http.StatusOK, |
| wantContents: "desired contents", |
| }, |
| { |
| name: "remote fetch of non-existent object returns not found", |
| endpoint: "/blobs/123456", |
| wantStatusCode: http.StatusNotFound, |
| }, |
| { |
| name: "remote fetch of invalid path returns bad request", |
| endpoint: "/invalid", |
| wantStatusCode: http.StatusBadRequest, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| // Set up the local package repository. |
| localRepo := emptyPackageRepo(t) |
| |
| // Override the GCS reader to effectively return a 404 unless the |
| // test case overrides it below. |
| getGCSReader = func(_ context.Context, _ *storage.Client, _, _ string) (io.ReadCloser, error) { |
| return nil, storage.ErrObjectNotExist |
| } |
| |
| // Add any contents to the repo (both locally and in the "remote") |
| // that the test case requires. |
| if tc.wantContents != "" { |
| if tc.local { |
| localPath := filepath.Join(localRepo, tc.endpoint) |
| if err := os.WriteFile(localPath, []byte(tc.wantContents), os.ModePerm); err != nil { |
| t.Fatalf("WriteFile(%s, %s) failed; got %s, want <nil> error", localPath, tc.wantContents, err) |
| } |
| } else { |
| getGCSReader = func(_ context.Context, _ *storage.Client, _, _ string) (io.ReadCloser, error) { |
| r := strings.NewReader(tc.wantContents) |
| return io.NopCloser(r), nil |
| } |
| } |
| } |
| |
| // Start the package server. |
| // We ignore the returned repoURL and blobURL to make testing |
| // invalid endpoints easier. |
| pkgSrv, err := NewPackageServer(ctx, localRepo, "", "", "", 8080) |
| if err != nil { |
| t.Fatalf("NewPackageServer failed; got %s, want <nil> error", err) |
| } |
| defer pkgSrv.Close() |
| |
| // Make a request to the endpoint and validate that we get the |
| // expected response. |
| addr := fmt.Sprintf("http://%s:8080%s", localhostPlaceholder, tc.endpoint) |
| res, err := http.Get(addr) |
| if err != nil { |
| t.Fatalf("http.Get(%s) failed; got %s, want <nil> error", addr, err) |
| } |
| defer res.Body.Close() |
| |
| if res.StatusCode != tc.wantStatusCode { |
| t.Errorf("got incorrect status code; got %d, want %d", res.StatusCode, tc.wantStatusCode) |
| } |
| if tc.wantContents != "" { |
| body, err := io.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("reading response body failed; got %s, want <nil> error", err) |
| } |
| if tc.wantContents != string(body) { |
| t.Errorf("got incorrect contents; got %q, want %q", string(body), tc.wantContents) |
| } |
| } |
| }) |
| } |
| } |