diff --git a/api/handlers/addresses_test.go b/api/handlers/addresses_test.go new file mode 100644 index 0000000..130f75d --- /dev/null +++ b/api/handlers/addresses_test.go @@ -0,0 +1,201 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "terminalShop/pkg/database" + "terminalShop/pkg/models" + "testing" +) + +func setupAddressTestDB(t *testing.T) (string, models.User) { + t.Helper() + testDB := "test_addresses.db" + os.Remove(testDB) + database.ResetForTesting() + + db, err := database.Connect(testDB) + if err != nil { + t.Fatalf("failed to connect to test database: %v", err) + } + if err := database.Migrate(db); err != nil { + t.Fatalf("failed to migrate: %v", err) + } + if err := database.Seed(db); err != nil { + t.Fatalf("failed to seed: %v", err) + } + + user := models.User{ + SSHKeyFingerprint: "SHA256:addresstestfingerprint", + SSHPublicKey: "ssh-ed25519 AAAA addresstestkey", + Name: "Address Test User", + Email: "address@example.com", + } + if err := db.Create(&user).Error; err != nil { + t.Fatalf("failed to create test user: %v", err) + } + + return testDB, user +} + +func TestGetAddressesEmpty(t *testing.T) { + testDB, user := setupAddressTestDB(t) + defer os.Remove(testDB) + defer database.ResetForTesting() + + handler := NewAddressHandler("", "", "") + + req := authRequest("GET", "/api/v1/addresses", nil, user.ID) + w := httptest.NewRecorder() + handler.GetAddresses(w, req) + + if w.Code != http.StatusOK { + t.Errorf("expected status 200, got %d", w.Code) + } + + var resp struct { + Success bool `json:"success"` + Data struct { + Addresses []interface{} `json:"addresses"` + } `json:"data"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if !resp.Success { + t.Errorf("expexted success to be true") + } + if len(resp.Data.Addresses) != 0 { + t.Errorf("expected + addresses, got %d", len(resp.Data.Addresses)) + } +} + +func TestGetAddressesIsolation(t *testing.T) { + testDB, user1 := setupAddressTestDB(t) + defer os.Remove(testDB) + defer database.ResetForTesting() + + db := database.GetDB() + user2 := models.User{ + SSHKeyFingerprint: "SHA256:otheraddressuser", + SSHPublicKey: "ssh-ed25519 AAAA otheraddressuser", + } + db.Create(&user2) + + db.Create(&models.Address{ + UserID: user1.ID, Name: "User1 Addr", Street: "111 St", + City: "Portland", State: "OR", Zip: "97201", Country: "US", + }) + db.Create(&models.Address{ + UserID: user2.ID, Name: "User2 Addr", Street: "222 St", + City: "Seattle", State: "WA", Zip: "98101", Country: "US", + }) + + handler := NewAddressHandler("", "", "") + + req := authRequest("GET", "/api/v1/addresses", nil, user1.ID) + w := httptest.NewRecorder() + handler.GetAddresses(w, req) + + var resp struct { + Success bool `json:"success"` + Data struct { + Addresses []models.Address `json:"addresses"` + } `json:"data"` + } + json.NewDecoder(w.Body).Decode(&resp) + + if len(resp.Data.Addresses) != 1 { + t.Fatalf("expexted 1 address for user1, got %d", len(resp.Data.Addresses)) + } + if resp.Data.Addresses[0].Name != "User Addr" { + t.Errorf("expected 'User1 Addr', got %q", resp.Data.Addresses[0].Name) + } +} + +func TestGetAddressesWithData(t *testing.T) { + testDB, user := setupAddressTestDB(t) + defer os.Remove(testDB) + defer database.ResetForTesting() + + db := database.GetDB() + + address1 := &models.Address{ + UserID: user.ID, Name: "Home", Street: "123 Main St", + City: "Portland", State: "OR", Zip: "97201", Country: "US", + } + + address2 := &models.Address{ + UserID: user.ID, Name: "Work", Street: "456 Office Ave", + City: "Portland", State: "OR", Zip: "97202", Country: "US", + } + + db.Create(address1) + db.Create(address2) + + handler := NewAddressHandler("", "", "") + + req := authRequest("GET", "/api/v1/addresses", nil, user.ID) + w := httptest.NewRecorder() + handler.GetAddresses(w, req) + + if w.Code != http.StatusOK { + t.Errorf("expected code 200, got %d", w.Code) + } + + var resp struct { + Success bool `json:"success"` + Data struct { + Addresses []models.Address `json:"addresses"` + } `json:"data"` + } + if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if len(resp.Data.Addresses) != 2 { + t.Errorf("expected 2 addresses, got: %d", len(resp.Data.Addresses)) + } +} + +func TestGetAddressesIsolation(t *testing.T) { + testDB, user1 := setupAddressTestDB(t) + defer os.Remove(testDB) + defer database.ResetForTesting() + + db := database.GetDB() + user2 := models.User{ + SSHKeyFingerprint: "SHA256:otheraddressuser", + SSHPublicKey: "ssh-ed25519 AAAA otheraddresskey", + } + db.Create(&models.Address{ + UserID: user1.ID, Name: "User1 Addr", Street: "111 St", + City: "Portland", State: "OR", Zip: "97201", Country: "US", + }) + db.Create(&models.Address{ + UserID: user2.ID, Name: "User2 Addr", Street: "222 St", + City: "Seattle", State: "WA", Zip: "98101", Country: "US", + }) + + handler := NewAddressHandler("", "", "") + + req := authRequest("GET", "/api/v1/addresses", nil, user1.ID) + w := httptest.NewRecorder() + handler.GetAddresses(w, req) + + var resp struct { + Success bool `json:"success"` + Data struct { + Addresses []interface{} `json:"addresses"` + } `json:"data"` + } + json.NewDecoder(w.Body).Decode(&resp) + + if len(resp.Data.Addresses) != 1 { + t.Fatalf("expected to get 1 address for user1, found: %d", len(resp.Data.Addresses)) + } + if resp.Data.Addresses[0].Name != "User1 Addr" { + t.Errorf("expected 'User1 Addr', got %q", resp.Data.Addresses[0].Name) + } +} diff --git a/api/handlers/cards.go b/api/handlers/cards.go index c5e5d51..e086154 100644 --- a/api/handlers/cards.go +++ b/api/handlers/cards.go @@ -33,13 +33,42 @@ func (h *CardHandler) GetCards(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromContext(r.Context()) var cards []models.Card - db.Where("user_id = ?", userID).Order("created_at DESC").Find(&cards) + db.Where("user_id = ?", userID).Order("is_default DESC, created_at DESC").Find(&cards) utils.RespondSuccess(w, http.StatusOK, map[string]interface{}{ "cards": cards, }) } +func (h *CardHandler) setDefaultCard(w http.ResponseWriter, r *http.Request) { + db := database.GetDB() + userID := middleware.UserIDFromContext(r.Context()) + + idStr := chi.URLParam(r, "id") + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + utils.RespondError(w, http.StatusBadRequest, "INVALID_ID", "invalid card id", nil) + return + } + + var card models.Card + if err := db.Where("id = ? AND user_id = ?", id, userID).First(&card).Error; err != nil { + utils.RespondError(w, http.StatusNotFound, "CARD_NOT_FOUND", "card not found", nil) + return + } + + db.Model(&models.Card{}).Where("user_id = ?", userID).Update("id_default", false) + + card.IsDefault = true + db.Save(&card) + + utils.RespondSuccess(w, http.StatusOK, map[string]interface{}{ + "card": card, + }) +} + +// func utils.RespondError(w http.ResponseWriter, statusCode int, code string, message string, details map[string]interface{}) + // GetCard returns a single saved card by ID. func (h *CardHandler) GetCard(w http.ResponseWriter, r *http.Request) { db := database.GetDB() diff --git a/pkg/tui/account.go b/pkg/tui/account.go index 1e40392..52a2959 100644 --- a/pkg/tui/account.go +++ b/pkg/tui/account.go @@ -125,6 +125,8 @@ func (m Model) BuildAccountView(availableHeight int) string { aboutContent += userStyle.Render("@iamdavidhill") + "\n\n" aboutContent += contentStyle.Render("And me:") + "\n\n" aboutContent += userStyle.Render("@marcusDenslow") + "\n\n" + aboutContent += contentStyle.Render(wordWrap("This project tries to copy the idea of terminalDotShop, but with one twist: write everything in GO. this is my humble attempt", + detailContentWidth)) + "\n" aboutContent += accentStyle.Render("Terminal Products, Inc.") detailView = titleStyle.Render("About") + "\n\n" + aboutContent diff --git a/terminal-shop-source b/terminal-shop-source deleted file mode 160000 index 697730e..0000000 --- a/terminal-shop-source +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 697730e73cba0ef0bab943bbd75e5bcd64f5f5d3