package gateway import ( "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/DeBrosOfficial/network/pkg/logging" ) func newRQLiteTestLogger() *logging.ColoredLogger { l, _ := logging.NewColoredLogger(logging.ComponentGeneral, false) return l } func TestRqliteBaseURL(t *testing.T) { tests := []struct { name string dsn string want string }{ {"empty defaults to localhost:5001", "", "http://localhost:5001"}, {"simple URL", "http://10.0.0.1:10000", "http://10.0.0.1:10000"}, {"strips query params", "http://10.0.0.1:10000?foo=bar", "http://10.0.0.1:10000"}, {"strips trailing slash", "http://10.0.0.1:10000/", "http://10.0.0.1:10000"}, {"strips both", "http://10.0.0.1:10000/?foo=bar", "http://10.0.0.1:10000"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gw := &Gateway{cfg: &Config{RQLiteDSN: tt.dsn}} got := gw.rqliteBaseURL() if got != tt.want { t.Errorf("rqliteBaseURL() = %q, want %q", got, tt.want) } }) } } func TestRqliteExportHandler_MethodNotAllowed(t *testing.T) { gw := &Gateway{cfg: &Config{RQLiteDSN: "http://localhost:5001"}} for _, method := range []string{http.MethodPost, http.MethodPut, http.MethodDelete} { req := httptest.NewRequest(method, "/v1/rqlite/export", nil) rr := httptest.NewRecorder() gw.rqliteExportHandler(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Errorf("method %s: got status %d, want %d", method, rr.Code, http.StatusMethodNotAllowed) } } } func TestRqliteExportHandler_Success(t *testing.T) { backupData := "fake-sqlite-binary-data" mockRQLite := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/db/backup" { t.Errorf("unexpected path: %s", r.URL.Path) w.WriteHeader(http.StatusNotFound) return } if r.Method != http.MethodGet { t.Errorf("unexpected method: %s", r.Method) w.WriteHeader(http.StatusMethodNotAllowed) return } w.Header().Set("Content-Type", "application/octet-stream") w.WriteHeader(http.StatusOK) w.Write([]byte(backupData)) })) defer mockRQLite.Close() gw := &Gateway{ cfg: &Config{RQLiteDSN: mockRQLite.URL}, logger: newRQLiteTestLogger(), } req := httptest.NewRequest(http.MethodGet, "/v1/rqlite/export", nil) rr := httptest.NewRecorder() gw.rqliteExportHandler(rr, req) if rr.Code != http.StatusOK { t.Fatalf("got status %d, want %d, body: %s", rr.Code, http.StatusOK, rr.Body.String()) } if ct := rr.Header().Get("Content-Type"); ct != "application/octet-stream" { t.Errorf("Content-Type = %q, want application/octet-stream", ct) } if cd := rr.Header().Get("Content-Disposition"); !strings.Contains(cd, "rqlite-export.db") { t.Errorf("Content-Disposition = %q, want to contain 'rqlite-export.db'", cd) } if body := rr.Body.String(); body != backupData { t.Errorf("body = %q, want %q", body, backupData) } } func TestRqliteExportHandler_RQLiteError(t *testing.T) { mockRQLite := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("rqlite internal error")) })) defer mockRQLite.Close() gw := &Gateway{ cfg: &Config{RQLiteDSN: mockRQLite.URL}, logger: newRQLiteTestLogger(), } req := httptest.NewRequest(http.MethodGet, "/v1/rqlite/export", nil) rr := httptest.NewRecorder() gw.rqliteExportHandler(rr, req) if rr.Code != http.StatusInternalServerError { t.Errorf("got status %d, want %d", rr.Code, http.StatusInternalServerError) } } func TestRqliteImportHandler_MethodNotAllowed(t *testing.T) { gw := &Gateway{cfg: &Config{RQLiteDSN: "http://localhost:5001"}} for _, method := range []string{http.MethodGet, http.MethodPut, http.MethodDelete} { req := httptest.NewRequest(method, "/v1/rqlite/import", nil) rr := httptest.NewRecorder() gw.rqliteImportHandler(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Errorf("method %s: got status %d, want %d", method, rr.Code, http.StatusMethodNotAllowed) } } } func TestRqliteImportHandler_WrongContentType(t *testing.T) { gw := &Gateway{cfg: &Config{RQLiteDSN: "http://localhost:5001"}} req := httptest.NewRequest(http.MethodPost, "/v1/rqlite/import", strings.NewReader("data")) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() gw.rqliteImportHandler(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("got status %d, want %d", rr.Code, http.StatusBadRequest) } } func TestRqliteImportHandler_Success(t *testing.T) { importData := "fake-sqlite-binary-data" var receivedBody string mockRQLite := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/db/load" { t.Errorf("unexpected path: %s", r.URL.Path) w.WriteHeader(http.StatusNotFound) return } if r.Method != http.MethodPost { t.Errorf("unexpected method: %s", r.Method) w.WriteHeader(http.StatusMethodNotAllowed) return } if ct := r.Header.Get("Content-Type"); ct != "application/octet-stream" { t.Errorf("Content-Type = %q, want application/octet-stream", ct) } body, _ := io.ReadAll(r.Body) receivedBody = string(body) w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) })) defer mockRQLite.Close() gw := &Gateway{ cfg: &Config{RQLiteDSN: mockRQLite.URL}, logger: newRQLiteTestLogger(), } req := httptest.NewRequest(http.MethodPost, "/v1/rqlite/import", strings.NewReader(importData)) req.Header.Set("Content-Type", "application/octet-stream") rr := httptest.NewRecorder() gw.rqliteImportHandler(rr, req) if rr.Code != http.StatusOK { t.Fatalf("got status %d, want %d, body: %s", rr.Code, http.StatusOK, rr.Body.String()) } if receivedBody != importData { t.Errorf("RQLite received body %q, want %q", receivedBody, importData) } } func TestRqliteImportHandler_RQLiteError(t *testing.T) { mockRQLite := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("load failed")) })) defer mockRQLite.Close() gw := &Gateway{ cfg: &Config{RQLiteDSN: mockRQLite.URL}, logger: newRQLiteTestLogger(), } req := httptest.NewRequest(http.MethodPost, "/v1/rqlite/import", strings.NewReader("data")) req.Header.Set("Content-Type", "application/octet-stream") rr := httptest.NewRecorder() gw.rqliteImportHandler(rr, req) if rr.Code != http.StatusInternalServerError { t.Errorf("got status %d, want %d", rr.Code, http.StatusInternalServerError) } }