diff --git a/pkg/api/oidc_clients.go b/pkg/api/oidc_clients.go index 83112fd..3a4710b 100644 --- a/pkg/api/oidc_clients.go +++ b/pkg/api/oidc_clients.go @@ -176,7 +176,7 @@ func RevokeClient(c *gin.Context) { er := models.LicenseError{ Status: http.StatusNotFound, Message: "Unable to delete oidc client", - Error: result.Error.Error(), + Error: "Oidc client not found", Path: c.Request.URL.Path, Timestamp: time.Now().Format(time.RFC3339), } diff --git a/tests/auth_test.go b/tests/auth_test.go index da9f836..a8cd771 100644 --- a/tests/auth_test.go +++ b/tests/auth_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/fossology/LicenseDb/pkg/db" "github.com/fossology/LicenseDb/pkg/models" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/assert" @@ -38,7 +39,12 @@ func TestLoginUser(t *testing.T) { } func TestCreateUser(t *testing.T) { + loginAs(t, "admin") + t.Run("Success", func(t *testing.T) { + // Cleanup before test + db.DB.Unscoped().Where("user_name = ?", "fossy-test").Delete(&models.User{}) + user := models.UserCreate{ UserName: ptr("fossy-test"), UserPassword: ptr("abc123"), @@ -54,8 +60,16 @@ func TestCreateUser(t *testing.T) { t.Errorf("Error unmarshalling JSON: %v", err) return } + + if len(res.Data) == 0 { + t.Fatalf("Expected created user data, but got none. Status: %d", w.Code) + } + assert.Equal(t, *user.UserName, *res.Data[0].UserName) assert.Equal(t, *user.UserLevel, *res.Data[0].UserLevel) + + // Cleanup after test + db.DB.Unscoped().Where("user_name = ?", "fossy-test").Delete(&models.User{}) }) t.Run("MissingFields", func(t *testing.T) { @@ -64,6 +78,9 @@ func TestCreateUser(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) }) t.Run("DuplicateUser", func(t *testing.T) { + // Cleanup before test + db.DB.Unscoped().Where("user_name = ?", "fossy2").Delete(&models.User{}) + user := models.UserCreate{ UserName: ptr("fossy2"), UserPassword: ptr("abc123"), @@ -79,6 +96,9 @@ func TestCreateUser(t *testing.T) { // Second request with same user should fail w2 := makeRequest("POST", "/users", user, true) assert.Equal(t, http.StatusConflict, w2.Code) + + // Cleanup after test + db.DB.Unscoped().Where("user_name = ?", "fossy2").Delete(&models.User{}) }) t.Run("Unauthorized", func(t *testing.T) { diff --git a/tests/dashboard_test.go b/tests/dashboard_test.go new file mode 100644 index 0000000..68540a6 --- /dev/null +++ b/tests/dashboard_test.go @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestGetDashboardData(t *testing.T) { + loginAs(t, "admin") + + t.Run("getDashboardData", func(t *testing.T) { + w := makeRequest("GET", "/dashboard", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.DashboardResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.NotNil(t, res.Data) + assert.GreaterOrEqual(t, res.Data.LicensesCount, int64(0)) + assert.GreaterOrEqual(t, res.Data.ObligationsCount, int64(0)) + assert.GreaterOrEqual(t, res.Data.UsersCount, int64(0)) + assert.GreaterOrEqual(t, res.Data.LicenseChangesSinceLastMonth, int64(0)) + riskFreqLen := 0 + if res.Data.RiskLicenseFrequency != nil { + riskFreqLen = len(res.Data.RiskLicenseFrequency) + } + categoryFreqLen := 0 + if res.Data.CategoryObligationFrequency != nil { + categoryFreqLen = len(res.Data.CategoryObligationFrequency) + } + assert.GreaterOrEqual(t, riskFreqLen, 0) + assert.GreaterOrEqual(t, categoryFreqLen, 0) + }) + + t.Run("getDashboardDataUnauthorized", func(t *testing.T) { + w := makeRequest("GET", "/dashboard", nil, false) + assert.Contains(t, []int{http.StatusOK, http.StatusUnauthorized}, w.Code) + }) +} diff --git a/tests/health_and_api_test.go b/tests/health_and_api_test.go new file mode 100644 index 0000000..4ab4799 --- /dev/null +++ b/tests/health_and_api_test.go @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestGetHealth(t *testing.T) { + t.Run("getHealthSuccess", func(t *testing.T) { + w := makeRequest("GET", "/health", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseError + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.Contains(t, res.Message, "Database is running") + }) +} + +func TestGetAPICollection(t *testing.T) { + t.Run("getAPICollectionSuccess", func(t *testing.T) { + w := makeRequest("GET", "/apiCollection", nil, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.APICollectionResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.NotNil(t, res.Data) + assert.NotNil(t, res.Data.Authenticated) + assert.NotNil(t, res.Data.UnAuthenticated) + }) +} diff --git a/tests/licenses_comprehensive_test.go b/tests/licenses_comprehensive_test.go new file mode 100644 index 0000000..a8a763b --- /dev/null +++ b/tests/licenses_comprehensive_test.go @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "bytes" + "encoding/json" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/fossology/LicenseDb/pkg/api" + "github.com/fossology/LicenseDb/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestFilterLicense(t *testing.T) { + loginAs(t, "admin") + + t.Run("filterByActive", func(t *testing.T) { + w := makeRequest("GET", "/licenses?active=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("filterByOSIApproved", func(t *testing.T) { + w := makeRequest("GET", "/licenses?osiapproved=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("filterByCopyleft", func(t *testing.T) { + w := makeRequest("GET", "/licenses?copyleft=false", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("filterBySpdxId", func(t *testing.T) { + license := models.LicenseCreateDTO{ + Shortname: "TEST-FILTER-SPDX", + Fullname: "Test Filter SPDX License", + Text: "Test license text", + Url: ptr("https://example.com"), + Notes: ptr("Test notes"), + Source: ptr("test"), + SpdxId: "LicenseRef-TEST-FILTER-SPDX", + Risk: ptr(int64(1)), + } + createW := makeRequest("POST", "/licenses", license, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + w := makeRequest("GET", "/licenses?spdxid=LicenseRef-TEST-FILTER-SPDX", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + if len(res.Data) > 0 { + assert.Equal(t, license.SpdxId, res.Data[0].SpdxId) + } + }) + + t.Run("filterWithPagination", func(t *testing.T) { + w := makeRequest("GET", "/licenses?page=1&limit=5", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(res.Data), 0) + assert.NotNil(t, res.Meta) + }) + + t.Run("filterWithSorting", func(t *testing.T) { + w := makeRequest("GET", "/licenses?sort_by=shortname&order_by=asc", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("filterWithInvalidSortBy", func(t *testing.T) { + w := makeRequest("GET", "/licenses?sort_by=invalid_field", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + }) +} + +func TestExportLicenses(t *testing.T) { + t.Run("exportSuccess", func(t *testing.T) { + w := makeRequest("GET", "/licenses/export", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var licenses []models.LicenseResponseDTO + if err := json.Unmarshal(w.Body.Bytes(), &licenses); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(licenses), 0) + assert.Contains(t, w.Header().Get("Content-Disposition"), "license-export") + }) +} + +func TestGetAllLicensePreviews(t *testing.T) { + t.Run("getActivePreviews", func(t *testing.T) { + w := makeRequest("GET", "/licenses/preview?active=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicensePreviewResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Licenses), 0) + }) + + t.Run("getInactivePreviews", func(t *testing.T) { + w := makeRequest("GET", "/licenses/preview?active=false", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicensePreviewResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("getPreviewsWithInvalidActive", func(t *testing.T) { + w := makeRequest("GET", "/licenses/preview?active=invalid", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestImportLicenses(t *testing.T) { + t.Run("importSuccess", func(t *testing.T) { + licenses := []models.LicenseImportDTO{ + { + Shortname: ptr("IMPORT-TEST-1"), + Fullname: ptr("Import Test License 1"), + Text: ptr("Test license text for import"), + Url: ptr("https://example.com/import1"), + Notes: ptr("Test notes for import"), + Source: ptr("test"), + SpdxId: ptr("LicenseRef-IMPORT-TEST-1"), + Risk: ptr(int64(2)), + }, + } + + jsonData, err := json.Marshal(licenses) + assert.NoError(t, err) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "licenses.json") + assert.NoError(t, err) + _, err = part.Write(jsonData) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/licenses/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ImportLicensesResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("importWithInvalidFile", func(t *testing.T) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "licenses.txt") + assert.NoError(t, err) + _, err = part.Write([]byte("not json")) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/licenses/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("importWithoutFile", func(t *testing.T) { + w := makeRequest("POST", "/licenses/import", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestSearchInLicense(t *testing.T) { + t.Run("searchWithFullText", func(t *testing.T) { + searchReq := models.SearchLicense{ + Field: "shortname", + Search: "full_text_search", + SearchTerm: "MIT", + } + + w := makeRequest("POST", "/search", searchReq, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("searchWithFuzzy", func(t *testing.T) { + searchReq := models.SearchLicense{ + Field: "fullname", + Search: "fuzzy", + SearchTerm: "MIT", + } + + w := makeRequest("POST", "/search", searchReq, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.LicenseResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("searchWithInvalidField", func(t *testing.T) { + searchReq := models.SearchLicense{ + Field: "invalid_field", + Search: "full_text_search", + SearchTerm: "MIT", + } + + w := makeRequest("POST", "/search", searchReq, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("searchWithInvalidAlgorithm", func(t *testing.T) { + searchReq := models.SearchLicense{ + Field: "shortname", + Search: "invalid_algorithm", + SearchTerm: "MIT", + } + + w := makeRequest("POST", "/search", searchReq, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("searchWithInvalidJSON", func(t *testing.T) { + w := makeRequest("POST", "/search", []byte("invalid json"), true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestGetSimilarLicenses(t *testing.T) { + t.Run("findSimilarLicenses", func(t *testing.T) { + similarityReq := models.SimilarityRequest{ + Text: "MIT License\n\nCopyright (c) \n\nPermission is hereby granted", + } + + w := makeRequest("POST", "/licenses/similarity", similarityReq, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ApiResponse[[]models.SimilarLicense] + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + if res.Data != nil { + assert.GreaterOrEqual(t, len(res.Data), 0) + } + }) + + t.Run("findSimilarWithEmptyText", func(t *testing.T) { + similarityReq := models.SimilarityRequest{ + Text: "", + } + + w := makeRequest("POST", "/licenses/similarity", similarityReq, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("findSimilarWithInvalidJSON", func(t *testing.T) { + w := makeRequest("POST", "/licenses/similarity", []byte("invalid json"), true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} diff --git a/tests/main_test.go b/tests/main_test.go index 66bd35e..31c779e 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -77,7 +77,12 @@ func TestMain(m *testing.M) { // makeRequest is a utility function for creating and sending HTTP requests during testing. func makeRequest(method, path string, body interface{}, isAuthenticated bool) *httptest.ResponseRecorder { - reqBody, _ := json.Marshal(body) + var reqBody []byte + if b, ok := body.([]byte); ok { + reqBody = b + } else { + reqBody, _ = json.Marshal(body) + } fullPath := baseURL + path req := httptest.NewRequest(method, fullPath, bytes.NewBuffer(reqBody)) req.Header.Set("Content-Type", "application/json") diff --git a/tests/obligation_maps_test.go b/tests/obligation_maps_test.go new file mode 100644 index 0000000..1011b87 --- /dev/null +++ b/tests/obligation_maps_test.go @@ -0,0 +1,295 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestGetObligationMapByObligationId(t *testing.T) { + loginAs(t, "admin") + + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + if len(licenseRes.Data) == 0 { + t.Skip("No licenses available for testing") + } + + dto := models.ObligationCreateDTO{ + Topic: "test-obligation-map", + Type: "RIGHT", + Text: "Test obligation text", + Classification: "GREEN", + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + LicenseIds: []uuid.UUID{licenseRes.Data[0].Id}, + } + createW := makeRequest("POST", "/obligations", dto, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + var createRes models.ObligationResponse + if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { + t.Fatalf("Failed to parse create response: %v", err) + } + if len(createRes.Data) == 0 { + t.Fatal("No obligation returned in create response") + } + obligationId := createRes.Data[0].Id + + t.Run("getObligationMapByObligationId", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/obligation/"+obligationId.String(), nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationMapResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + if len(res.Data) > 0 { + assert.Equal(t, dto.Topic, res.Data[0].Topic) + assert.GreaterOrEqual(t, len(res.Data[0].Licenses), 0) + } + }) + + t.Run("getObligationMapForNonExistingObligation", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/obligation/"+uuid.New().String(), nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("getObligationMapWithInvalidId", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/obligation/invalid-id", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestGetObligationMapByLicenseId(t *testing.T) { + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + if len(licenseRes.Data) == 0 { + t.Skip("No licenses available for testing") + } + licenseId := licenseRes.Data[0].Id + + t.Run("getObligationMapByLicenseId", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/license/"+licenseId.String(), nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationMapResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("getObligationMapForNonExistingLicense", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/license/"+uuid.New().String(), nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("getObligationMapWithInvalidId", func(t *testing.T) { + w := makeRequest("GET", "/obligation_maps/license/invalid-id", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestPatchObligationMap(t *testing.T) { + dto := models.ObligationCreateDTO{ + Topic: "test-patch-obligation-map", + Type: "RIGHT", + Text: "Test obligation text", + Classification: "GREEN", + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + } + createW := makeRequest("POST", "/obligations", dto, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + var createRes models.ObligationResponse + if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { + t.Fatalf("Failed to parse create response: %v", err) + } + if len(createRes.Data) == 0 { + t.Fatal("No obligation returned in create response") + } + obligationId := createRes.Data[0].Id + + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + if len(licenseRes.Data) == 0 { + t.Skip("No licenses available for testing") + } + licenseId := licenseRes.Data[0].Id + + t.Run("patchObligationMapAddLicense", func(t *testing.T) { + mapInput := models.LicenseMapInput{ + MapInput: []models.LicenseMapElement{ + { + Id: licenseId, + Add: true, + }, + }, + } + + w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInput, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationMapResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("patchObligationMapRemoveLicense", func(t *testing.T) { + mapInputAdd := models.LicenseMapInput{ + MapInput: []models.LicenseMapElement{ + { + Id: licenseId, + Add: true, + }, + }, + } + _ = makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInputAdd, true) + + mapInputRemove := models.LicenseMapInput{ + MapInput: []models.LicenseMapElement{ + { + Id: licenseId, + Add: false, + }, + }, + } + + w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", mapInputRemove, true) + assert.Equal(t, http.StatusOK, w.Code) + }) + + t.Run("patchObligationMapForNonExistingObligation", func(t *testing.T) { + mapInput := models.LicenseMapInput{ + MapInput: []models.LicenseMapElement{ + { + Id: licenseId, + Add: true, + }, + }, + } + + w := makeRequest("PATCH", "/obligation_maps/obligation/"+uuid.New().String()+"/license", mapInput, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("patchObligationMapWithInvalidJSON", func(t *testing.T) { + w := makeRequest("PATCH", "/obligation_maps/obligation/"+obligationId.String()+"/license", "invalid", true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestUpdateLicenseInObligationMap(t *testing.T) { + dto := models.ObligationCreateDTO{ + Topic: "test-update-obligation-map", + Type: "RIGHT", + Text: "Test obligation text", + Classification: "GREEN", + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + } + createW := makeRequest("POST", "/obligations", dto, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + var createRes models.ObligationResponse + if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { + t.Fatalf("Failed to parse create response: %v", err) + } + if len(createRes.Data) == 0 { + t.Fatal("No obligation returned in create response") + } + obligationId := createRes.Data[0].Id + + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + if len(licenseRes.Data) == 0 { + t.Skip("No licenses available for testing") + } + licenseId := licenseRes.Data[0].Id + + t.Run("updateLicenseInObligationMap", func(t *testing.T) { + licenseListInput := models.LicenseListInput{ + LicenseIds: []uuid.UUID{licenseId}, + } + + w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", licenseListInput, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationMapResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("updateLicenseInObligationMapWithEmptyList", func(t *testing.T) { + licenseListInput := models.LicenseListInput{ + LicenseIds: []uuid.UUID{}, + } + + w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", licenseListInput, true) + assert.Equal(t, http.StatusOK, w.Code) + }) + + t.Run("updateLicenseInObligationMapForNonExistingObligation", func(t *testing.T) { + licenseListInput := models.LicenseListInput{ + LicenseIds: []uuid.UUID{licenseId}, + } + + w := makeRequest("PUT", "/obligation_maps/obligation/"+uuid.New().String()+"/license", licenseListInput, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("updateLicenseInObligationMapWithInvalidJSON", func(t *testing.T) { + w := makeRequest("PUT", "/obligation_maps/obligation/"+obligationId.String()+"/license", "invalid", true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} diff --git a/tests/obligations_comprehensive_test.go b/tests/obligations_comprehensive_test.go new file mode 100644 index 0000000..5732f5a --- /dev/null +++ b/tests/obligations_comprehensive_test.go @@ -0,0 +1,337 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "bytes" + "encoding/json" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/fossology/LicenseDb/pkg/api" + "github.com/fossology/LicenseDb/pkg/models" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestGetAllObligation(t *testing.T) { + loginAs(t, "admin") + + t.Run("getAllActiveObligations", func(t *testing.T) { + w := makeRequest("GET", "/obligations?active=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("getAllObligationsWithPagination", func(t *testing.T) { + w := makeRequest("GET", "/obligations?page=1&limit=5", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.NotNil(t, res.Meta) + }) + + t.Run("getAllObligationsWithOrderBy", func(t *testing.T) { + w := makeRequest("GET", "/obligations?order_by=desc", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) +} + +func TestGetObligation(t *testing.T) { + dto := models.ObligationCreateDTO{ + Topic: "test-get-obligation", + Type: "RIGHT", + Text: "Test obligation text", + Classification: "GREEN", + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + } + createW := makeRequest("POST", "/obligations", dto, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + var createRes models.ObligationResponse + if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { + t.Fatalf("Failed to parse create response: %v", err) + } + if len(createRes.Data) == 0 { + t.Fatal("No obligation returned in create response") + } + obligationId := createRes.Data[0].Id + + t.Run("getExistingObligation", func(t *testing.T) { + w := makeRequest("GET", "/obligations/"+obligationId.String(), nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + if len(res.Data) > 0 { + assert.Equal(t, dto.Topic, res.Data[0].Topic) + } + }) + + t.Run("getNonExistingObligation", func(t *testing.T) { + w := makeRequest("GET", "/obligations/"+uuid.New().String(), nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("getObligationWithInvalidId", func(t *testing.T) { + w := makeRequest("GET", "/obligations/invalid-id", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestGetAllObligationPreviews(t *testing.T) { + t.Run("getActivePreviews", func(t *testing.T) { + w := makeRequest("GET", "/obligations/preview?active=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ObligationPreviewResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("getPreviewsWithInvalidActive", func(t *testing.T) { + w := makeRequest("GET", "/obligations/preview?active=invalid", nil, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestGetObligationAudits(t *testing.T) { + dto := models.ObligationCreateDTO{ + Topic: "test-audit-obligation", + Type: "RIGHT", + Text: "Test obligation text", + Classification: "GREEN", + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + } + createW := makeRequest("POST", "/obligations", dto, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + var createRes models.ObligationResponse + if err := json.Unmarshal(createW.Body.Bytes(), &createRes); err != nil { + t.Fatalf("Failed to parse create response: %v", err) + } + if len(createRes.Data) == 0 { + t.Fatal("No obligation returned in create response") + } + obligationId := createRes.Data[0].Id + + t.Run("getObligationAudits", func(t *testing.T) { + w := makeRequest("GET", "/obligations/"+obligationId.String()+"/audits", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.AuditResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("getObligationAuditsWithPagination", func(t *testing.T) { + w := makeRequest("GET", "/obligations/"+obligationId.String()+"/audits?page=1&limit=5", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.AuditResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("getAuditsForNonExistingObligation", func(t *testing.T) { + w := makeRequest("GET", "/obligations/"+uuid.New().String()+"/audits", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.AuditResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.Equal(t, 0, len(res.Data)) + }) +} + +func TestExportObligations(t *testing.T) { + t.Run("exportSuccess", func(t *testing.T) { + w := makeRequest("GET", "/obligations/export", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var obligations []models.ObligationResponseDTO + if err := json.Unmarshal(w.Body.Bytes(), &obligations); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.GreaterOrEqual(t, len(obligations), 0) + assert.Contains(t, w.Header().Get("Content-Disposition"), "obligations-export") + }) +} + +func TestImportObligations(t *testing.T) { + t.Run("importSuccess", func(t *testing.T) { + licenseW := makeRequest("GET", "/licenses", nil, true) + assert.Equal(t, http.StatusOK, licenseW.Code) + + var licenseRes models.LicenseResponse + if err := json.Unmarshal(licenseW.Body.Bytes(), &licenseRes); err != nil { + t.Fatalf("Failed to parse license response: %v", err) + } + + var licenseId *uuid.UUID + if len(licenseRes.Data) > 0 { + licenseId = &licenseRes.Data[0].Id + } + + obligations := []models.ObligationFileDTO{ + { + Topic: ptr("IMPORT-OBLIGATION-1"), + Type: ptr("RIGHT"), + Text: ptr("Test obligation text for import"), + Classification: ptr("GREEN"), + Comment: ptr("Test comment"), + Active: ptr(true), + TextUpdatable: ptr(false), + Category: ptr("GENERAL"), + }, + } + + if licenseId != nil { + obligations[0].LicenseIds = &[]uuid.UUID{*licenseId} + } + + jsonData, err := json.Marshal(obligations) + assert.NoError(t, err) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "obligations.json") + assert.NoError(t, err) + _, err = part.Write(jsonData) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/obligations/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ImportObligationsResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("importWithInvalidFile", func(t *testing.T) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", "obligations.txt") + assert.NoError(t, err) + _, err = part.Write([]byte("not json")) + assert.NoError(t, err) + writer.Close() + + fullPath := baseURL + "/obligations/import" + req := httptest.NewRequest("POST", fullPath, body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("importWithoutFile", func(t *testing.T) { + fullPath := baseURL + "/obligations/import" + req := httptest.NewRequest("POST", fullPath, nil) + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} + +func TestGetSimilarObligations(t *testing.T) { + t.Run("findSimilarObligations", func(t *testing.T) { + similarityReq := models.SimilarityRequest{ + Text: "You must include the copyright notice and this permission notice in all copies", + } + + w := makeRequest("POST", "/obligations/similarity", similarityReq, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.ApiResponse[[]models.SimilarObligation] + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + if res.Data != nil { + assert.GreaterOrEqual(t, len(res.Data), 0) + } + }) + + t.Run("findSimilarWithEmptyText", func(t *testing.T) { + similarityReq := models.SimilarityRequest{ + Text: "", + } + + w := makeRequest("POST", "/obligations/similarity", similarityReq, true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("findSimilarWithInvalidJSON", func(t *testing.T) { + fullPath := baseURL + "/obligations/similarity" + req := httptest.NewRequest("POST", fullPath, bytes.NewBuffer([]byte("invalid json"))) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+AuthToken) + w := httptest.NewRecorder() + api.Router().ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +} diff --git a/tests/oidc_clients_test.go b/tests/oidc_clients_test.go new file mode 100644 index 0000000..395dd95 --- /dev/null +++ b/tests/oidc_clients_test.go @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestGetUserOidcClients(t *testing.T) { + loginAs(t, "admin") + + t.Run("getUserOidcClients", func(t *testing.T) { + w := makeRequest("GET", "/oidcClients", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.OidcClientsResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.NotNil(t, res.Data) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("GET", "/oidcClients", nil, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestAddOidcClient(t *testing.T) { + t.Run("addOidcClientSuccess", func(t *testing.T) { + oidcClient := models.CreateDeleteOidcClientDTO{ + ClientId: "test-client-id-1", + } + + w := makeRequest("POST", "/oidcClients", oidcClient, true) + assert.Equal(t, http.StatusCreated, w.Code) + + var res models.OidcClientsResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + if len(res.Data) > 0 { + assert.Equal(t, oidcClient.ClientId, res.Data[0].ClientId) + } + }) + + t.Run("addDuplicateOidcClient", func(t *testing.T) { + oidcClient := models.CreateDeleteOidcClientDTO{ + ClientId: "test-client-id-2", + } + + w1 := makeRequest("POST", "/oidcClients", oidcClient, true) + assert.Equal(t, http.StatusCreated, w1.Code) + + w2 := makeRequest("POST", "/oidcClients", oidcClient, true) + assert.Equal(t, http.StatusConflict, w2.Code) + }) + + t.Run("addOidcClientWithInvalidJSON", func(t *testing.T) { + w := makeRequest("POST", "/oidcClients", "invalid", true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + oidcClient := models.CreateDeleteOidcClientDTO{ + ClientId: "test-client-id-3", + } + w := makeRequest("POST", "/oidcClients", oidcClient, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestRevokeClient(t *testing.T) { + oidcClient := models.CreateDeleteOidcClientDTO{ + ClientId: "test-revoke-client", + } + addW := makeRequest("POST", "/oidcClients", oidcClient, true) + assert.Equal(t, http.StatusCreated, addW.Code) + + t.Run("revokeClientSuccess", func(t *testing.T) { + w := makeRequest("DELETE", "/oidcClients", oidcClient, true) + assert.Equal(t, http.StatusNoContent, w.Code) + }) + + t.Run("revokeNonExistingClient", func(t *testing.T) { + nonExistingClient := models.CreateDeleteOidcClientDTO{ + ClientId: "non-existing-client", + } + w := makeRequest("DELETE", "/oidcClients", nonExistingClient, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("revokeClientWithInvalidJSON", func(t *testing.T) { + w := makeRequest("DELETE", "/oidcClients", "invalid", true) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("DELETE", "/oidcClients", oidcClient, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} diff --git a/tests/users_comprehensive_test.go b/tests/users_comprehensive_test.go new file mode 100644 index 0000000..90c2860 --- /dev/null +++ b/tests/users_comprehensive_test.go @@ -0,0 +1,340 @@ +// SPDX-FileCopyrightText: 2026 Krishi Agrawal +// +// SPDX-License-Identifier: GPL-2.0-only + +package test + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/fossology/LicenseDb/pkg/models" + "github.com/stretchr/testify/assert" +) + +func TestGetAllUser(t *testing.T) { + loginAs(t, "admin") + + t.Run("getAllActiveUsers", func(t *testing.T) { + w := makeRequest("GET", "/users?active=true", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.GreaterOrEqual(t, len(res.Data), 0) + }) + + t.Run("getAllInactiveUsers", func(t *testing.T) { + w := makeRequest("GET", "/users?active=false", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + }) + + t.Run("getAllUsersWithPagination", func(t *testing.T) { + w := makeRequest("GET", "/users?page=1&limit=5", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + assert.NotNil(t, res.Meta) + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("GET", "/users", nil, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestGetUser(t *testing.T) { + t.Run("getExistingUser", func(t *testing.T) { + w := makeRequest("GET", "/users/fossy_superadmin", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + if len(res.Data) > 0 { + assert.Equal(t, "fossy_superadmin", *res.Data[0].UserName) + } + }) + + t.Run("getNonExistingUser", func(t *testing.T) { + w := makeRequest("GET", "/users/nonexistent_user", nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("GET", "/users/fossy_superadmin", nil, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestGetUserProfile(t *testing.T) { + t.Run("getOwnProfile", func(t *testing.T) { + w := makeRequest("GET", "/users/profile", nil, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.Equal(t, http.StatusOK, res.Status) + if len(res.Data) > 0 { + assert.NotNil(t, res.Data[0].UserName) + } + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("GET", "/users/profile", nil, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestUpdateUser(t *testing.T) { + testUser := models.UserCreate{ + UserName: ptr("test_update_user"), + UserPassword: ptr("testpass123"), + UserLevel: ptr("USER"), + DisplayName: ptr("Test Update User"), + UserEmail: ptr("testupdate@example.com"), + } + createW := makeRequest("POST", "/users", testUser, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + t.Run("updateUserSuccess", func(t *testing.T) { + updateData := models.UserUpdate{ + DisplayName: ptr("Updated Display Name"), + UserEmail: ptr("updated@example.com"), + } + + w := makeRequest("PATCH", "/users/test_update_user", updateData, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + if len(res.Data) > 0 { + assert.Equal(t, *updateData.DisplayName, *res.Data[0].DisplayName) + } + }) + + t.Run("updateNonExistingUser", func(t *testing.T) { + updateData := models.UserUpdate{ + DisplayName: ptr("Updated Display Name"), + } + + w := makeRequest("PATCH", "/users/nonexistent_user", updateData, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("updateWithInvalidData", func(t *testing.T) { + updateData := models.UserUpdate{ + UserEmail: ptr("invalid-email"), + } + + w := makeRequest("PATCH", "/users/test_update_user", updateData, true) + assert.Contains(t, []int{http.StatusOK, http.StatusBadRequest}, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + updateData := models.UserUpdate{ + DisplayName: ptr("Updated Display Name"), + } + w := makeRequest("PATCH", "/users/test_update_user", updateData, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestUpdateProfile(t *testing.T) { + t.Run("updateOwnProfile", func(t *testing.T) { + profileUpdate := models.ProfileUpdate{ + DisplayName: ptr("My Updated Profile Name"), + } + + w := makeRequest("PATCH", "/users", profileUpdate, true) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.UserResponse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + if len(res.Data) > 0 { + assert.Equal(t, *profileUpdate.DisplayName, *res.Data[0].DisplayName) + } + }) + + t.Run("updatePassword", func(t *testing.T) { + originalToken := AuthToken + + profileUpdate := models.ProfileUpdate{ + UserPassword: ptr("newpassword123"), + } + + w := makeRequest("PATCH", "/users", profileUpdate, true) + assert.Equal(t, http.StatusOK, w.Code) + + loginData := models.UserLogin{ + Username: "fossy_superadmin", + Userpassword: "newpassword123", + } + loginW := makeRequest("POST", "/login", loginData, false) + if loginW.Code == http.StatusOK { + var loginRes models.TokenResonse + if err := json.Unmarshal(loginW.Body.Bytes(), &loginRes); err == nil { + AuthToken = loginRes.Data.AccessToken + resetPassword := models.ProfileUpdate{ + UserPassword: ptr("fossy"), + } + resetW := makeRequest("PATCH", "/users", resetPassword, true) + assert.Equal(t, http.StatusOK, resetW.Code) + + loginData.Userpassword = "fossy" + loginW2 := makeRequest("POST", "/login", loginData, false) + if loginW2.Code == http.StatusOK { + var loginRes2 models.TokenResonse + if err := json.Unmarshal(loginW2.Body.Bytes(), &loginRes2); err == nil { + AuthToken = loginRes2.Data.AccessToken + } + } else { + AuthToken = originalToken + } + } + } + }) + + t.Run("updateWithInvalidData", func(t *testing.T) { + profileUpdate := models.ProfileUpdate{ + UserEmail: ptr("invalid-email"), + } + + w := makeRequest("PATCH", "/users", profileUpdate, true) + assert.Contains(t, []int{http.StatusOK, http.StatusBadRequest}, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + profileUpdate := models.ProfileUpdate{ + DisplayName: ptr("Updated Name"), + } + w := makeRequest("PATCH", "/users", profileUpdate, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestDeleteUser(t *testing.T) { + testUser := models.UserCreate{ + UserName: ptr("test_delete_user"), + UserPassword: ptr("testpass123"), + UserLevel: ptr("USER"), + DisplayName: ptr("Test Delete User"), + UserEmail: ptr("testdelete@example.com"), + } + createW := makeRequest("POST", "/users", testUser, true) + assert.Equal(t, http.StatusCreated, createW.Code) + + t.Run("deleteExistingUser", func(t *testing.T) { + w := makeRequest("DELETE", "/users/test_delete_user", nil, true) + assert.Equal(t, http.StatusNoContent, w.Code) + }) + + t.Run("deleteNonExistingUser", func(t *testing.T) { + w := makeRequest("DELETE", "/users/nonexistent_user", nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("deleteAlreadyDeletedUser", func(t *testing.T) { + w := makeRequest("DELETE", "/users/test_delete_user", nil, true) + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("unauthorized", func(t *testing.T) { + w := makeRequest("DELETE", "/users/test_delete_user", nil, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + +func TestVerifyRefreshToken(t *testing.T) { + loginData := models.UserLogin{ + Username: "fossy_superadmin", + Userpassword: "fossy", + } + + loginW := makeRequest("POST", "/login", loginData, false) + + if loginW.Code != http.StatusOK { + loginData.Userpassword = "newpassword123" + loginW = makeRequest("POST", "/login", loginData, false) + } + + if loginW.Code != http.StatusOK { + t.Skipf("Cannot login to get refresh token: status %d", loginW.Code) + } + + var loginRes models.TokenResonse + if err := json.Unmarshal(loginW.Body.Bytes(), &loginRes); err != nil { + t.Fatalf("Failed to parse login response: %v", err) + } + + if loginRes.Data.RefreshToken == "" { + t.Skip("Refresh token not available in login response") + } + + t.Run("verifyValidRefreshToken", func(t *testing.T) { + refreshReq := models.RefreshToken{ + RefreshToken: loginRes.Data.RefreshToken, + } + + w := makeRequest("POST", "/refresh-token", refreshReq, false) + assert.Equal(t, http.StatusOK, w.Code) + + var res models.TokenResonse + if err := json.Unmarshal(w.Body.Bytes(), &res); err != nil { + t.Errorf("Error unmarshalling JSON: %v", err) + return + } + assert.NotEmpty(t, res.Data.AccessToken) + }) + + t.Run("verifyInvalidRefreshToken", func(t *testing.T) { + refreshReq := models.RefreshToken{ + RefreshToken: "invalid_token", + } + + w := makeRequest("POST", "/refresh-token", refreshReq, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) + + t.Run("verifyWithEmptyToken", func(t *testing.T) { + refreshReq := models.RefreshToken{ + RefreshToken: "", + } + + w := makeRequest("POST", "/refresh-token", refreshReq, false) + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) + + t.Run("verifyWithInvalidJSON", func(t *testing.T) { + w := makeRequest("POST", "/refresh-token", "invalid", false) + assert.Equal(t, http.StatusBadRequest, w.Code) + }) +}