Skip to content

Commit

Permalink
Test CheckForServiceAvailability (#25)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kavir1698 authored Mar 28, 2024
1 parent 09efe04 commit 325dd1e
Show file tree
Hide file tree
Showing 2 changed files with 306 additions and 34 deletions.
121 changes: 87 additions & 34 deletions datasetUtils/checkForServiceAvailability.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import (
"log"
"net/http"
"os"

"gopkg.in/yaml.v2"

"github.com/fatih/color"
)

Expand All @@ -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)
Expand All @@ -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
}
219 changes: 219 additions & 0 deletions datasetUtils/checkForServiceAvailability_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}

0 comments on commit 325dd1e

Please sign in to comment.