From 325dd1e4a4b0d1020c4a3d4c2468d2a3bc087d32 Mon Sep 17 00:00:00 2001 From: Ali Vahdati <3798865+kavir1698@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:46:45 +0100 Subject: [PATCH] Test CheckForServiceAvailability (#25) * Comment the function * Add tests for reading the YAML file * Remove unused lib * Fix a test * Fix the GitHubMainLocation address * Refactor CheckForServiceAvailability * Write a test for logServiceUnavailability * Refactor handleServiceUnavailability to use Error instead of os.Exit(1) Add a unit test too * Add a test for determineStatusAndEnv --- datasetUtils/checkForServiceAvailability.go | 121 +++++++--- .../checkForServiceAvailability_test.go | 219 ++++++++++++++++++ 2 files changed, 306 insertions(+), 34 deletions(-) create mode 100644 datasetUtils/checkForServiceAvailability_test.go diff --git a/datasetUtils/checkForServiceAvailability.go b/datasetUtils/checkForServiceAvailability.go index 33cc691..a551e75 100644 --- a/datasetUtils/checkForServiceAvailability.go +++ b/datasetUtils/checkForServiceAvailability.go @@ -6,9 +6,7 @@ import ( "log" "net/http" "os" - "gopkg.in/yaml.v2" - "github.com/fatih/color" ) @@ -28,48 +26,66 @@ type ServiceAvailability struct { Qa OverallAvailability } +var GitHubMainLocation = "https://raw.githubusercontent.com/paulscherrerinstitute/scicat-cli/main" + +// CheckForServiceAvailability checks the availability of the dataset ingestor service. +// It fetches a YAML file from GitHubMainLocation, parses it, and logs the service availability status. func CheckForServiceAvailability(client *http.Client, testenvFlag bool, autoarchiveFlag bool) { - resp, err := client.Get(DeployLocation + "datasetIngestorServiceAvailability.yml") + s, err := getServiceAvailability(client) if err != nil { - fmt.Println("No Information about Service Availability") + log.Printf("Failed to get service availability: %v", err) return } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - log.Println("No Information about Service Availability") - log.Printf("Error: Got %s fetching %s\n", resp.Status, DeployLocation + "datasetIngestorServiceAvailability.yml") - return + + status, env := determineStatusAndEnv(s, testenvFlag) + + logPlannedDowntime(status, env) + + err = handleServiceUnavailability(status, env, autoarchiveFlag) + if err != nil { + log.Printf("Error: %v", err) + os.Exit(1) } +} - yamlFile, err := io.ReadAll(resp.Body) +func getServiceAvailability(client *http.Client) (ServiceAvailability, error) { + yamlFile, err := readYAMLFile(client) if err != nil { - fmt.Println("Can not read service availability file for this application") - return + return ServiceAvailability{}, err } - + s := ServiceAvailability{} err = yaml.Unmarshal(yamlFile, &s) if err != nil { - log.Fatalf("Unmarshal of availabilty file failed: %v\n%s", err, yamlFile) + return ServiceAvailability{}, fmt.Errorf("Unmarshal of availabilty file failed: %v\n%s", err, yamlFile) } - var status OverallAvailability - var env string - // define default value - status = OverallAvailability{Availability{"on", "", "", ""}, Availability{"on", "", "", ""}} + + return s, nil +} + +func determineStatusAndEnv(s ServiceAvailability, testenvFlag bool) (OverallAvailability, string) { + status := OverallAvailability{Availability{"on", "", "", ""}, Availability{"on", "", "", ""}} + env := "production" + if testenvFlag { if (OverallAvailability{}) != s.Qa { status = s.Qa } env = "test" - } else { + } else { if (OverallAvailability{}) != s.Production { - status = s.Production - } - env = "production" + status = s.Production + } } - defer color.Unset() + + return status, env +} +func logPlannedDowntime(status OverallAvailability, env string) { + // Reset the terminal color after the function returns + defer color.Unset() + + // Log the planned downtime for the ingest and archive services, if any if status.Ingest.Downfrom != "" { color.Set(color.FgYellow) fmt.Printf("Next planned downtime for %s data catalog ingest service is scheduled at %v\n", env, status.Ingest.Downfrom) @@ -86,18 +102,55 @@ func CheckForServiceAvailability(client *http.Client, testenvFlag bool, autoarch color.Set(color.FgYellow) fmt.Printf("It is scheduled to last until %v\n", status.Archive.Downto) } +} + +func handleServiceUnavailability(status OverallAvailability, env string, autoarchiveFlag bool) error { + // If the ingest service is not available, log a message and return an error if status.Ingest.Status != "on" { - color.Set(color.FgRed) - log.Printf("The %s data catalog is currently not available for ingesting new datasets\n", env) - log.Printf("Planned downtime until %v. Reason: %s\n", status.Ingest.Downto, status.Ingest.Comment) - color.Unset() - os.Exit(1) + logServiceUnavailability("ingest", env, status.Ingest) + return fmt.Errorf("ingest service is unavailable") } + + // If the archive service is not available and autoarchiveFlag is set, log a message and return an error if autoarchiveFlag && status.Archive.Status != "on" { - color.Set(color.FgRed) - log.Printf("The %s data catalog is currently not available for archiving new datasets\n", env) - log.Printf("Planned downtime until %v. Reason: %s\n", status.Archive.Downto, status.Archive.Comment) - color.Unset() - os.Exit(1) + logServiceUnavailability("archive", env, status.Archive) + return fmt.Errorf("archive service is unavailable") + } + + return nil +} + +func logServiceUnavailability(serviceName string, env string, availability Availability) { + color.Set(color.FgRed) + log.Printf("The %s data catalog is currently not available for %sing new datasets\n", env, serviceName) + log.Printf("Planned downtime until %v. Reason: %s\n", availability.Downto, availability.Comment) + color.Unset() +} + +func readYAMLFile(client *http.Client) ([]byte, error) { + // Construct the URL of the service availability YAML file + yamlURL := fmt.Sprintf("%s/cmd/datasetIngestor/datasetIngestorServiceAvailability.yml", GitHubMainLocation) + + // Send a GET request to fetch the service availability YAML file + resp, err := client.Get(yamlURL) + if err != nil { + fmt.Println("No Information about Service Availability") + return nil, fmt.Errorf("failed to fetch the service availability YAML file: %w", err) + } + defer resp.Body.Close() + + // If the HTTP status code is not 200 (OK), log a message and return + if resp.StatusCode != 200 { + log.Println("No Information about Service Availability") + log.Printf("Error: Got %s fetching %s\n", resp.Status, yamlURL) + return nil, fmt.Errorf("got %s fetching %s", resp.Status, yamlURL) + } + + // Read the entire body of the response (the YAML file) + yamlFile, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("Can not read service availability file for this application") } + + return yamlFile, nil } diff --git a/datasetUtils/checkForServiceAvailability_test.go b/datasetUtils/checkForServiceAvailability_test.go new file mode 100644 index 0000000..ee02769 --- /dev/null +++ b/datasetUtils/checkForServiceAvailability_test.go @@ -0,0 +1,219 @@ +package datasetUtils + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + "bytes" + "gopkg.in/yaml.v2" + "log" + "os" + "strings" + "reflect" +) + +func TestReadYAMLFile(t *testing.T) { + // Create a mock HTTP server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/repos/paulscherrerinstitute/scicat-cli/releases/latest": + // Respond with a mock release + fmt.Fprintln(w, `{"tag_name": "v1.0.0"}`) + case "/paulscherrerinstitute/scicat-cli/releases/download/v1.0.0/cmd/datasetIngestor/datasetIngestorServiceAvailability.yml": + // Respond with a mock YAML file + fmt.Fprintln(w, "mock: YAML file") + default: + http.NotFound(w, r) + } + })) + defer server.Close() + + // Update the GitHubAPI and GitHubMainLocation variables to point to the mock server + oldGitHubAPI := GitHubAPI + oldGitHubMainLocation := GitHubMainLocation + GitHubAPI = server.URL + "/repos/paulscherrerinstitute/scicat-cli/releases/latest" + GitHubMainLocation = server.URL + "/paulscherrerinstitute/scicat-cli/releases/download/v1.0.0" + defer func() { + // Restore the original variables after the test + GitHubAPI = oldGitHubAPI + GitHubMainLocation = oldGitHubMainLocation + }() + + // Create a new HTTP client + client := &http.Client{} + + // Call the function + yamlFile, err := readYAMLFile(client) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Check that the function returned the expected YAML file + expected := []byte("mock: YAML file\n") + if !bytes.Equal(yamlFile, expected) { + t.Errorf("Expected %q, got %q", expected, yamlFile) + } +} + +func TestReadYAMLFileIntegration(t *testing.T) { + // Create a new HTTP client + client := &http.Client{} + + // Call the function + yamlFile, err := readYAMLFile(client) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Check that the function returned a non-empty file + if len(yamlFile) == 0 { + t.Errorf("Expected a non-empty YAML file, got an empty file") + } +} + +func TestYAMLStructure(t *testing.T) { + // The test will fail if the indentation of yamlFile is not correct + yamlFile := []byte(` +production: + ingest: + status: on + archive: + status: on +qa: + ingest: + status: on + archive: + status: on +`) + + var serviceAvailability ServiceAvailability + + err := yaml.Unmarshal(yamlFile, &serviceAvailability) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + checkService := func(service Availability, serviceName string) { + if service.Status != "on" { + if service.Downfrom == "" { + t.Errorf("Expected 'downfrom' for %s when status is 'down'", serviceName) + } + + if service.Downto == "" { + t.Errorf("Expected 'downto' for %s when status is 'down'", serviceName) + } + + if service.Comment == "" { + t.Errorf("Expected 'comment' for %s when status is 'down'", serviceName) + } + } + } + + checkService(serviceAvailability.Production.Ingest, "production ingest") + checkService(serviceAvailability.Production.Archive, "production archive") + checkService(serviceAvailability.Qa.Ingest, "qa ingest") + checkService(serviceAvailability.Qa.Archive, "qa archive") +} + +func TestLogServiceUnavailability(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer func() { + log.SetOutput(os.Stderr) + }() + + serviceName := "ingest" + env := "test" + availability := Availability{ + Status: "off", + Downfrom: "2022-01-01T00:00:00Z", + Downto: "2022-01-02T00:00:00Z", + Comment: "Maintenance", + } + + logServiceUnavailability(serviceName, env, availability) + + want := "The test data catalog is currently not available for ingesting new datasets" + if got := buf.String(); !strings.Contains(got, want) { + t.Errorf("logServiceUnavailability() = %q, want %q", got, want) + } + + want = "Planned downtime until 2022-01-02T00:00:00Z. Reason: Maintenance" + if got := buf.String(); !strings.Contains(got, want) { + t.Errorf("logServiceUnavailability() = %q, want %q", got, want) + } +} + +func TestHandleServiceUnavailability(t *testing.T) { + tests := []struct { + name string + status OverallAvailability + env string + autoarchiveFlag bool + wantErr bool + }{ + { + name: "ingest service unavailable", + status: OverallAvailability{ + Ingest: Availability{Status: "off"}, + Archive: Availability{Status: "on"}, + }, + env: "test", + autoarchiveFlag: false, + wantErr: true, + }, + // Add more test cases here. + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := handleServiceUnavailability(tt.status, tt.env, tt.autoarchiveFlag) + if (err != nil) != tt.wantErr { + t.Errorf("handleServiceUnavailability() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDetermineStatusAndEnv(t *testing.T) { + tests := []struct { + name string + s ServiceAvailability + testenvFlag bool + wantStatus OverallAvailability + wantEnv string + }{ + { + name: "test environment", + s: ServiceAvailability{ + Qa: OverallAvailability{ + Ingest: Availability{Status: "off"}, + Archive: Availability{Status: "on"}, + }, + Production: OverallAvailability{ + Ingest: Availability{Status: "on"}, + Archive: Availability{Status: "on"}, + }, + }, + testenvFlag: true, + wantStatus: OverallAvailability{ + Ingest: Availability{Status: "off"}, + Archive: Availability{Status: "on"}, + }, + wantEnv: "test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotStatus, gotEnv := determineStatusAndEnv(tt.s, tt.testenvFlag) + if !reflect.DeepEqual(gotStatus, tt.wantStatus) { + t.Errorf("determineStatusAndEnv() gotStatus = %v, want %v", gotStatus, tt.wantStatus) + } + if gotEnv != tt.wantEnv { + t.Errorf("determineStatusAndEnv() gotEnv = %v, want %v", gotEnv, tt.wantEnv) + } + }) + } +}