package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type DashboardIntegrationTestSuite struct { suite.Suite setup *TestServerSetup } func (s *DashboardIntegrationTestSuite) SetupSuite() { s.setup = setupTestServer(s.T()) } func (s *DashboardIntegrationTestSuite) TearDownSuite() { s.setup.Close() } func (s *DashboardIntegrationTestSuite) TestGetSections_EndToEndFlow() { token := s.setup.Token // Create test library libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Test Library", false) // Execute API call url := fmt.Sprintf("%s/api/dashboard/sections?library_id=%s", s.setup.Server.URL, libraryID) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusOK, resp.StatusCode) var response map[string]interface{} err = json.NewDecoder(resp.Body).Decode(&response) require.NoError(s.T(), err) sections, ok := response["sections"].([]interface{}) require.True(s.T(), ok, "sections should be an array") require.Len(s.T(), sections, 4, "Should have 4 system collections") // Verify response structure sectionMap := make(map[string]map[string]interface{}) for _, sec := range sections { section := sec.(map[string]interface{}) sectionMap[section["id"].(string)] = section // Verify field types assert.IsType(s.T(), false, section["is_system"], "is_system should be boolean") assert.IsType(s.T(), "", section["title"], "title should be string") assert.IsType(s.T(), "", section["description"], "description should be string") assert.IsType(s.T(), "", section["icon"], "icon should be string") assert.IsType(s.T(), float64(0), section["priority"], "priority should be number") } // Verify all system collections exist assert.Contains(s.T(), sectionMap, "continue-reading") assert.Contains(s.T(), sectionMap, "recently-added") assert.Contains(s.T(), sectionMap, "recently-read") assert.Contains(s.T(), sectionMap, "not-started") // Verify continue-reading is a system collection continueReading := sectionMap["continue-reading"] assert.True(s.T(), continueReading["is_system"].(bool), "continue-reading should be system collection") assert.Equal(s.T(), "📖", continueReading["icon"], "icon should match") } func (s *DashboardIntegrationTestSuite) TestGetSections_MissingLibraryID() { token := s.setup.Token req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/dashboard/sections", nil) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestGetSections_InvalidLibraryID() { token := s.setup.Token req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/dashboard/sections?library_id=invalid-uuid", nil) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestGetSections_Unauthorized() { req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/dashboard/sections?library_id="+uuid.New().String(), nil) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestUpdatePreferences_Success() { token := s.setup.Token libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Test Library", false) reqBody := map[string]interface{}{ "library_id": libraryID, "hidden_collections": []string{"not-started"}, "collection_order": []string{"recently-added", "continue-reading", "recently-read"}, "items_per_section": 20, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("PUT", s.setup.Server.URL+"/api/dashboard/preferences", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusOK, resp.StatusCode) var response map[string]interface{} err = json.NewDecoder(resp.Body).Decode(&response) require.NoError(s.T(), err) assert.Contains(s.T(), response, "hidden_collections") assert.Contains(s.T(), response, "collection_order") assert.Contains(s.T(), response, "items_per_section") } func (s *DashboardIntegrationTestSuite) TestUpdatePreferences_Unauthorized() { reqBody := map[string]interface{}{ "library_id": uuid.New().String(), "hidden_collections": []string{}, "collection_order": []string{}, "items_per_section": 20, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("PUT", s.setup.Server.URL+"/api/dashboard/preferences", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_InvalidName() { token := s.setup.Token reqBody := map[string]interface{}{ "collection_name": "invalid-collection-name", } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", s.setup.Server.URL+"/api/dashboard/restore-system-collection", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_Unauthorized() { reqBody := map[string]interface{}{ "collection_name": "continue-reading", } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", s.setup.Server.URL+"/api/dashboard/restore-system-collection", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode) } func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_ValidNames() { token := s.setup.Token validCollections := []string{"Continue Reading", "Recently Added", "Recently Read", "Not Started"} for _, collName := range validCollections { s.T().Run(collName, func(t *testing.T) { reqBody := map[string]interface{}{ "collection_name": collName, } body, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", s.setup.Server.URL+"/api/dashboard/restore-system-collection", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) require.NoError(s.T(), err) defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) assert.Equal(t, http.StatusOK, resp.StatusCode) var response map[string]interface{} err = json.NewDecoder(resp.Body).Decode(&response) require.NoError(s.T(), err) assert.Contains(t, response, "message") }) } } func TestDashboardIntegrationTestSuite(t *testing.T) { suite.Run(t, new(DashboardIntegrationTestSuite)) }