-
Notifications
You must be signed in to change notification settings - Fork 0
Improve scheduler/oracle test coverage and stabilize CI workflow runtime/toolchain #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestOracleSetGetAndCopySemantics(t *testing.T) { | ||
| o := NewOracle() | ||
| o.SetPrice("node-1", 0.05, "mock") | ||
|
|
||
| entry, ok := o.GetPrice("node-1") | ||
| if !ok { | ||
| t.Fatal("expected price entry") | ||
| } | ||
| entry.Price = 999 | ||
|
|
||
| entry2, ok := o.GetPrice("node-1") | ||
| if !ok { | ||
| t.Fatal("expected price entry") | ||
| } | ||
| if entry2.Price == 999 { | ||
| t.Fatal("expected GetPrice to return a copy") | ||
| } | ||
| } | ||
|
|
||
| func TestHandleGetPriceFoundAndNotFound(t *testing.T) { | ||
| o := NewOracle() | ||
| o.SetPrice("node-1", 0.05, "mock") | ||
|
|
||
| foundReq := httptest.NewRequest(http.MethodGet, "/price/node-1", nil) | ||
| foundReq.SetPathValue("providerID", "node-1") | ||
| foundRec := httptest.NewRecorder() | ||
| o.HandleGetPrice(foundRec, foundReq) | ||
| if foundRec.Code != http.StatusOK { | ||
| t.Fatalf("expected 200, got %d", foundRec.Code) | ||
| } | ||
|
|
||
| var payload PriceEntry | ||
| if err := json.Unmarshal(foundRec.Body.Bytes(), &payload); err != nil { | ||
| t.Fatalf("decode response: %v", err) | ||
| } | ||
| if payload.ProviderID != "node-1" { | ||
| t.Fatalf("expected provider id node-1, got %s", payload.ProviderID) | ||
| } | ||
|
|
||
| missReq := httptest.NewRequest(http.MethodGet, "/price/missing", nil) | ||
| missReq.SetPathValue("providerID", "missing") | ||
| missRec := httptest.NewRecorder() | ||
| o.HandleGetPrice(missRec, missReq) | ||
| if missRec.Code != http.StatusNotFound { | ||
| t.Fatalf("expected 404, got %d", missRec.Code) | ||
| } | ||
| } | ||
|
|
||
| func TestHandleGetAllPricesReturnsArray(t *testing.T) { | ||
| o := NewOracle() | ||
|
|
||
| req := httptest.NewRequest(http.MethodGet, "/prices", nil) | ||
| rec := httptest.NewRecorder() | ||
| o.HandleGetAllPrices(rec, req) | ||
| if rec.Code != http.StatusOK { | ||
| t.Fatalf("expected 200, got %d", rec.Code) | ||
| } | ||
|
|
||
| var entries []PriceEntry | ||
| if err := json.Unmarshal(rec.Body.Bytes(), &entries); err != nil { | ||
| t.Fatalf("decode response: %v", err) | ||
| } | ||
| if len(entries) != 0 { | ||
| t.Fatalf("expected empty array, got %d entries", len(entries)) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package main | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestFetchAllPricesContainsExpectedProvidersWithPositiveValues(t *testing.T) { | ||
| prices := FetchAllPrices() | ||
| if len(prices) == 0 { | ||
| t.Fatal("expected non-empty price map") | ||
| } | ||
|
|
||
| required := []string{"aws-t3-medium", "gcp-n1-standard-2", "node-1"} | ||
| for _, id := range required { | ||
| p, ok := prices[id] | ||
| if !ok { | ||
| t.Fatalf("expected provider %s in feed", id) | ||
| } | ||
| if p.PricePerHour <= 0 { | ||
| t.Fatalf("expected positive price for %s, got %f", id, p.PricePerHour) | ||
| } | ||
| if p.Source == "" { | ||
| t.Fatalf("expected non-empty source for %s", id) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestJitterIsWithinFivePercent(t *testing.T) { | ||
| base := 100.0 | ||
| min := base * 0.95 | ||
| max := base * 1.05 | ||
|
|
||
| for i := 0; i < 1000; i++ { | ||
| v := jitter(base) | ||
| if v < min || v > max { | ||
| t.Fatalf("jitter value %f out of bounds [%f, %f]", v, min, max) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,115 @@ | ||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||
| "net/http/httptest" | ||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func TestProviderManagerRegisterGetAndCopySemantics(t *testing.T) { | ||||||||||||||||||||||||
| pm := NewProviderManager() | ||||||||||||||||||||||||
| pm.Register(Provider{ID: "p1", CPU: 4, MemoryMB: 8192, PricePerHour: 0.1, Reputation: 80}) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if pm.GetCount() != 1 { | ||||||||||||||||||||||||
| t.Fatalf("expected 1 active provider, got %d", pm.GetCount()) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| got, ok := pm.Get("p1") | ||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||
| t.Fatal("expected provider to exist") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| got.CPU = 999 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| gotAgain, ok := pm.Get("p1") | ||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||
| t.Fatal("expected provider to exist") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if gotAgain.CPU == 999 { | ||||||||||||||||||||||||
| t.Fatal("expected Get to return a copy, not original pointer") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| active := pm.GetActive() | ||||||||||||||||||||||||
| if len(active) != 1 { | ||||||||||||||||||||||||
| t.Fatalf("expected one active provider, got %d", len(active)) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| active[0].MemoryMB = 1 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| active2 := pm.GetActive() | ||||||||||||||||||||||||
| if active2[0].MemoryMB == 1 { | ||||||||||||||||||||||||
| t.Fatal("expected GetActive to return copies, not original pointers") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func TestProviderManagerDeregisterAndUpdateReputationClamp(t *testing.T) { | ||||||||||||||||||||||||
| pm := NewProviderManager() | ||||||||||||||||||||||||
| pm.Register(Provider{ID: "p1", CPU: 2, MemoryMB: 2048, PricePerHour: 0.05, Reputation: 50}) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if !pm.Deregister("p1") { | ||||||||||||||||||||||||
| t.Fatal("expected deregister to return true for existing provider") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if pm.GetCount() != 0 { | ||||||||||||||||||||||||
| t.Fatalf("expected no active providers, got %d", pm.GetCount()) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if pm.Deregister("missing") { | ||||||||||||||||||||||||
| t.Fatal("expected deregister to return false for missing provider") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| pm.UpdateReputation("p1", 1000) | ||||||||||||||||||||||||
| p, _ := pm.Get("p1") | ||||||||||||||||||||||||
| if p.Reputation != 100 { | ||||||||||||||||||||||||
| t.Fatalf("expected reputation clamp to 100, got %d", p.Reputation) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| pm.UpdateReputation("p1", -1000) | ||||||||||||||||||||||||
| p, _ = pm.Get("p1") | ||||||||||||||||||||||||
| if p.Reputation != 0 { | ||||||||||||||||||||||||
| t.Fatalf("expected reputation clamp to 0, got %d", p.Reputation) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func TestHandleRegisterProviderValidationAndSuccess(t *testing.T) { | ||||||||||||||||||||||||
| pm := NewProviderManager() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| invalidReq := httptest.NewRequest(http.MethodPost, "/providers/register", strings.NewReader(`{"id":"","cpu":0,"memoryMB":0}`)) | ||||||||||||||||||||||||
| invalidRec := httptest.NewRecorder() | ||||||||||||||||||||||||
| pm.HandleRegisterProvider(invalidRec, invalidReq) | ||||||||||||||||||||||||
| if invalidRec.Code != http.StatusBadRequest { | ||||||||||||||||||||||||
| t.Fatalf("expected 400 for invalid payload, got %d", invalidRec.Code) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| validReq := httptest.NewRequest(http.MethodPost, "/providers/register", strings.NewReader(`{"id":"p1","cpu":4,"memoryMB":8192,"pricePerHour":0.1}`)) | ||||||||||||||||||||||||
| validRec := httptest.NewRecorder() | ||||||||||||||||||||||||
| pm.HandleRegisterProvider(validRec, validReq) | ||||||||||||||||||||||||
| if validRec.Code != http.StatusCreated { | ||||||||||||||||||||||||
| t.Fatalf("expected 201 for valid payload, got %d", validRec.Code) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| var body map[string]string | ||||||||||||||||||||||||
| if err := json.Unmarshal(validRec.Body.Bytes(), &body); err != nil { | ||||||||||||||||||||||||
| t.Fatalf("decode response: %v", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if body["id"] != "p1" { | ||||||||||||||||||||||||
| t.Fatalf("expected id p1, got %q", body["id"]) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func TestHandleListProvidersReturnsEmptyArrayNotNull(t *testing.T) { | ||||||||||||||||||||||||
| pm := NewProviderManager() | ||||||||||||||||||||||||
| req := httptest.NewRequest(http.MethodGet, "/providers", nil) | ||||||||||||||||||||||||
| rec := httptest.NewRecorder() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| pm.HandleListProviders(rec, req) | ||||||||||||||||||||||||
| if rec.Code != http.StatusOK { | ||||||||||||||||||||||||
| t.Fatalf("expected 200, got %d", rec.Code) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| var providers []Provider | ||||||||||||||||||||||||
| if err := json.Unmarshal(rec.Body.Bytes(), &providers); err != nil { | ||||||||||||||||||||||||
| t.Fatalf("decode response: %v", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if len(providers) != 0 { | ||||||||||||||||||||||||
| t.Fatalf("expected empty provider list, got %d", len(providers)) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+108
to
+114
|
||||||||||||||||||||||||
| var providers []Provider | |
| if err := json.Unmarshal(rec.Body.Bytes(), &providers); err != nil { | |
| t.Fatalf("decode response: %v", err) | |
| } | |
| if len(providers) != 0 { | |
| t.Fatalf("expected empty provider list, got %d", len(providers)) | |
| } | |
| body := strings.TrimSpace(rec.Body.String()) | |
| if body != "[]" { | |
| t.Fatalf("expected empty JSON array [], got %q", body) | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't validate the "array not null" behavior: unmarshalling
nullinto a slice results innilwithlen==0, so the test would pass even if the handler encodednull. To assert the response is an empty array, compare the raw body (trimmed) against[], or unmarshal into*[]PriceEntryand ensure the pointer is non-nil.