package api_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/v10networks/nmon/internal/api" "github.com/v10networks/nmon/internal/auth" ) type fakeSetup struct { install bool done bool } func (f *fakeSetup) InstallMode() bool { return f.install } func (f *fakeSetup) SetupCompleted(context.Context) (bool, error) { return f.done, nil } func (f *fakeSetup) SignIn(context.Context, string, api.SetupSignInBody) (api.SetupSignIn, error) { return api.SetupSignIn{}, api.ErrSetupClosed } func (f *fakeSetup) Complete(context.Context, string) (api.SetupComplete, error) { return api.SetupComplete{}, api.ErrSetupNoSignIn } func TestSetupStateIsPublicAndCarriesNoSecrets(t *testing.T) { fs := &fakeSetup{install: true} e := newAuthedEnv(t, func(d *api.Deps) { d.Setup = fs }) rec := httptest.NewRecorder() e.top.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/setup", nil)) if rec.Code != http.StatusOK { t.Fatalf("GET /setup without credentials = %d %s", rec.Code, rec.Body) } var s api.SetupState if err := json.Unmarshal(rec.Body.Bytes(), &s); err != nil || !s.InstallMode || s.Defaults.BreakGlassUser != "admin" || s.Defaults.MinSignInLength != auth.MinBreakGlassPassword || s.Defaults.PollInterval != "5m" { t.Fatalf("state = %s (%v)", rec.Body, err) } if strings.Contains(strings.ToLower(rec.Body.String()), "password\":") { t.Fatalf("state leaks a password field: %s", rec.Body) } } func TestSetupPostsAnswer409WhenSetupIsClosed(t *testing.T) { e := newEnvDeps(t, 0, func(d *api.Deps) { d.Setup = &fakeSetup{done: true}; d.AlertPolicy = &fakePolicyHost{} }) isProblem(t, e.do("POST", "/api/v1/setup/signin", `{"username":"admin","password":"a long enough one"}`), http.StatusConflict) isProblem(t, e.do("POST", "/api/v1/setup/recommended-policies", `{}`), http.StatusConflict) isProblem(t, e.do("POST", "/api/v1/setup/complete", ``), http.StatusConflict) } // allRulesHost knows every rule the recommended set binds. type allRulesHost struct{ fakePolicyHost } func (allRulesHost) Rules() []api.AlertRuleInfo { var out []api.AlertRuleInfo for name, level := range map[string]string{"device_down": "device", "device_warning": "device", "interface_errors": "interface", "cpu_high": "device", "memory_high": "device", "disk_full": "component"} { out = append(out, api.AlertRuleInfo{Name: name, Level: level, Severity: "warning"}) } return out } func TestRecommendedPoliciesAreCreatedOnceAndIdempotently(t *testing.T) { host := &allRulesHost{} e := newEnvDeps(t, 0, func(d *api.Deps) { d.Setup = &fakeSetup{install: true}; d.AlertPolicy = host }) var first, second api.SetupPolicies rec := e.do("POST", "/api/v1/setup/recommended-policies", `{}`) if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &first) != nil || len(first.Created) != 6 { t.Fatalf("first run: %d %s", rec.Code, rec.Body) } rec = e.do("POST", "/api/v1/setup/recommended-policies", `{}`) if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &second) != nil || len(second.Created) != 0 || len(second.Existing) != 6 { t.Fatalf("second run must create nothing: %d %s", rec.Code, rec.Body) } var list api.AlertPolicyList e.getJSON(t, "/api/v1/alert-policies", &list) if len(list.Items) != 6 || !list.Items[0].Scope.All || !list.Items[0].Enabled { t.Fatalf("policies = %+v", list) } } func TestInstallRoutesAreRealRoutes(t *testing.T) { have := map[string]bool{} for _, r := range api.Routes() { have[r.Pattern] = true } for _, pat := range api.InstallRoutes() { if !have[pat] { t.Errorf("install mode allows %q, which is not an API route", pat) } } }