|
|
|
@@ -0,0 +1,572 @@
|
|
|
|
|
package store
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"git.pengzhan.dev/noteplace-server/internal/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// setupTestStore creates a temporary JSON file and a new Store instance for testing.
|
|
|
|
|
// It returns the Store, the path to the temporary file, and a cleanup function.
|
|
|
|
|
func setupTestStore(t *testing.T, initialData *DB) (*Store, string, func()) {
|
|
|
|
|
tempDir, err := os.MkdirTemp("", "noteplace_test_")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
|
|
|
}
|
|
|
|
|
tempFilePath := filepath.Join(tempDir, "test_db.json")
|
|
|
|
|
|
|
|
|
|
if initialData != nil {
|
|
|
|
|
data, err := json.MarshalIndent(initialData, "", " ")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to marshal initial data: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := os.WriteFile(tempFilePath, data, 0644); err != nil {
|
|
|
|
|
t.Fatalf("Failed to write initial data to temp file: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store, err := New(tempFilePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to create new store: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup := func() {
|
|
|
|
|
_ = os.RemoveAll(tempDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return store, tempFilePath, cleanup
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// func TestNew(t *testing.T) {
|
|
|
|
|
// t.Run("creates new store with empty file", func(t *testing.T) {
|
|
|
|
|
// _, tempFilePath, cleanup := setupTestStore(t, nil)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// store, err := New(tempFilePath)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("New() error = %v, wantErr %v", err, nil)
|
|
|
|
|
// }
|
|
|
|
|
// if store == nil {
|
|
|
|
|
// t.Fatal("New() returned nil store")
|
|
|
|
|
// }
|
|
|
|
|
// if store.Data == nil {
|
|
|
|
|
// t.Fatal("New() store.Data is nil")
|
|
|
|
|
// }
|
|
|
|
|
// if len(store.Data.Users) != 0 || len(store.Data.Places) != 0 || len(store.Data.Attributes) != 0 || len(store.Data.Ratings) != 0 {
|
|
|
|
|
// t.Errorf("New() store.Data not empty for new file, got %+v", store.Data)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("loads existing data from file", func(t *testing.T) {
|
|
|
|
|
// initialUser := models.User{ID: "user1", GoogleID: "google1", DisplayName: "Test User"}
|
|
|
|
|
// initialDB := &DB{Users: []models.User{initialUser}}
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Users) != 1 || store.Data.Users[0].ID != initialUser.ID {
|
|
|
|
|
// t.Errorf("New() did not load initial user, got %+v", store.Data.Users)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("returns error for invalid file path", func(t *testing.T) {
|
|
|
|
|
// // Attempt to create a store with a path that's likely to cause permission issues
|
|
|
|
|
// store, err := New("/nonexistent/path/to/db.json")
|
|
|
|
|
// if err == nil {
|
|
|
|
|
// t.Fatal("New() did not return error for invalid path")
|
|
|
|
|
// }
|
|
|
|
|
// if store != nil {
|
|
|
|
|
// t.Fatal("New() returned non-nil store for invalid path")
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestLoad(t *testing.T) {
|
|
|
|
|
// t.Run("loads data from valid JSON file", func(t *testing.T) {
|
|
|
|
|
// initialUser := models.User{ID: "user1", GoogleID: "google1", DisplayName: "Test User"}
|
|
|
|
|
// initialDB := &DB{Users: []models.User{initialUser}}
|
|
|
|
|
// store, tempFilePath, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// // Clear store data to ensure load actually reloads
|
|
|
|
|
// store.Data = &DB{}
|
|
|
|
|
// if err := store.load(); err != nil {
|
|
|
|
|
// t.Fatalf("load() error = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Users) != 1 || store.Data.Users[0].ID != initialUser.ID {
|
|
|
|
|
// t.Errorf("load() did not load expected user, got %+v", store.Data.Users)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("initializes empty DB if file does not exist", func(t *testing.T) {
|
|
|
|
|
// tempDir, err := ioutil.TempDir("", "noteplace_test_")
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to create temp dir: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// tempFilePath := filepath.Join(tempDir, "non_existent_db.json")
|
|
|
|
|
// defer os.RemoveAll(tempDir)
|
|
|
|
|
|
|
|
|
|
// store := &Store{path: tempFilePath, Data: &DB{}}
|
|
|
|
|
// if err := store.load(); err != nil {
|
|
|
|
|
// t.Fatalf("load() error for non-existent file = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Users) != 0 || len(store.Data.Places) != 0 || len(store.Data.Attributes) != 0 || len(store.Data.Ratings) != 0 {
|
|
|
|
|
// t.Errorf("load() did not initialize empty DB for non-existent file, got %+v", store.Data)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("returns error for invalid JSON file", func(t *testing.T) {
|
|
|
|
|
// tempDir, err := ioutil.TempDir("", "noteplace_test_")
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to create temp dir: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// tempFilePath := filepath.Join(tempDir, "invalid_db.json")
|
|
|
|
|
// defer os.RemoveAll(tempDir)
|
|
|
|
|
|
|
|
|
|
// if err := ioutil.WriteFile(tempFilePath, []byte("{invalid json"), 0644); err != nil {
|
|
|
|
|
// t.Fatalf("Failed to write invalid JSON to temp file: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// store := &Store{path: tempFilePath, Data: &DB{}}
|
|
|
|
|
// if err := store.load(); err == nil {
|
|
|
|
|
// t.Fatal("load() did not return error for invalid JSON")
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestSave(t *testing.T) {
|
|
|
|
|
// t.Run("saves current data to file", func(t *testing.T) {
|
|
|
|
|
// store, tempFilePath, cleanup := setupTestStore(t, nil)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// testUser := models.User{ID: "user1", GoogleID: "google1", Name: "Test User"}
|
|
|
|
|
// store.Data.Users = append(store.Data.Users, testUser)
|
|
|
|
|
|
|
|
|
|
// if err := store.Save(); err != nil {
|
|
|
|
|
// t.Fatalf("Save() error = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Verify content of the file
|
|
|
|
|
// fileContent, err := ioutil.ReadFile(tempFilePath)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to read saved file: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// var loadedDB DB
|
|
|
|
|
// if err := json.Unmarshal(fileContent, &loadedDB); err != nil {
|
|
|
|
|
// t.Fatalf("Failed to unmarshal saved file content: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(loadedDB.Users) != 1 || loadedDB.Users[0].ID != testUser.ID {
|
|
|
|
|
// t.Errorf("Save() did not save expected user, got %+v", loadedDB.Users)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("returns error if file cannot be written", func(t *testing.T) {
|
|
|
|
|
// // Create a read-only directory to simulate unwritable path
|
|
|
|
|
// tempDir, err := ioutil.TempDir("", "noteplace_test_")
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to create temp dir: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// defer os.RemoveAll(tempDir)
|
|
|
|
|
|
|
|
|
|
// readOnlyFilePath := filepath.Join(tempDir, "read_only_db.json")
|
|
|
|
|
// // Make the directory read-only
|
|
|
|
|
// if err := os.Chmod(tempDir, 0444); err != nil {
|
|
|
|
|
// t.Fatalf("Failed to chmod temp dir: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// store := &Store{path: readOnlyFilePath, Data: &DB{}}
|
|
|
|
|
// store.Data.Users = append(store.Data.Users, models.User{ID: "user1"})
|
|
|
|
|
|
|
|
|
|
// if err := store.Save(); err == nil {
|
|
|
|
|
// t.Fatal("Save() did not return error for unwritable path")
|
|
|
|
|
// }
|
|
|
|
|
// // Change permissions back to allow cleanup
|
|
|
|
|
// os.Chmod(tempDir, 0755)
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestCreatePlace(t *testing.T) {
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, nil)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// place := models.Place{ID: "place1", Name: "Test Place", Category: "Restaurant"}
|
|
|
|
|
// err := store.CreatePlace(place)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("CreatePlace() error = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Places) != 1 || store.Data.Places[0].ID != place.ID {
|
|
|
|
|
// t.Errorf("CreatePlace() did not add place, got %+v", store.Data.Places)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Verify it's saved to disk
|
|
|
|
|
// reloadedStore, err := New(store.path)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// if len(reloadedStore.Data.Places) != 1 || reloadedStore.Data.Places[0].ID != place.ID {
|
|
|
|
|
// t.Errorf("CreatePlace() did not save place to disk, got %+v", reloadedStore.Data.Places)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestGetPlaceByID(t *testing.T) {
|
|
|
|
|
// initialPlace := models.Place{ID: "place1", Name: "Test Place"}
|
|
|
|
|
// initialDB := &DB{Places: []models.Place{initialPlace}}
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// t.Run("finds existing place", func(t *testing.T) {
|
|
|
|
|
// foundPlace, found := store.GetPlaceByID("place1")
|
|
|
|
|
// if !found {
|
|
|
|
|
// t.Fatal("GetPlaceByID() did not find existing place")
|
|
|
|
|
// }
|
|
|
|
|
// if foundPlace.ID != initialPlace.ID {
|
|
|
|
|
// t.Errorf("GetPlaceByID() got place %+v, want %+v", foundPlace, initialPlace)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("does not find non-existent place", func(t *testing.T) {
|
|
|
|
|
// _, found := store.GetPlaceByID("nonexistent")
|
|
|
|
|
// if found {
|
|
|
|
|
// t.Fatal("GetPlaceByID() found non-existent place")
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestGetRatingsByPlaceID(t *testing.T) {
|
|
|
|
|
// rating1 := models.Rating{ID: "r1", PlaceID: "place1", UserID: "u1", Score: 5}
|
|
|
|
|
// rating2 := models.Rating{ID: "r2", PlaceID: "place1", UserID: "u2", Score: 4}
|
|
|
|
|
// rating3 := models.Rating{ID: "r3", PlaceID: "place2", UserID: "u1", Score: 3}
|
|
|
|
|
// initialDB := &DB{Ratings: []models.Rating{rating1, rating2, rating3}}
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// t.Run("finds multiple ratings for a place", func(t *testing.T) {
|
|
|
|
|
// ratings := store.GetRatingsByPlaceID("place1")
|
|
|
|
|
// if len(ratings) != 2 {
|
|
|
|
|
// t.Fatalf("GetRatingsByPlaceID() got %d ratings, want 2", len(ratings))
|
|
|
|
|
// }
|
|
|
|
|
// expected := []models.Rating{rating1, rating2}
|
|
|
|
|
// if !reflect.DeepEqual(ratings, expected) {
|
|
|
|
|
// t.Errorf("GetRatingsByPlaceID() got %+v, want %+v", ratings, expected)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("returns empty slice for place with no ratings", func(t *testing.T) {
|
|
|
|
|
// ratings := store.GetRatingsByPlaceID("place3")
|
|
|
|
|
// if len(ratings) != 0 {
|
|
|
|
|
// t.Errorf("GetRatingsByPlaceID() got %d ratings, want 0 for place3", len(ratings))
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestGetAttributesByPlace(t *testing.T) {
|
|
|
|
|
// place1 := models.Place{ID: "p1", Category: "Restaurant"}
|
|
|
|
|
// place2 := models.Place{ID: "p2", Category: "Cafe"}
|
|
|
|
|
|
|
|
|
|
// attrCommonRest := models.Attribute{ID: "a1", Scope: "COMMON", PlaceCategory: "Restaurant", Name: "Outdoor Seating"}
|
|
|
|
|
// attrSpecificP1 := models.Attribute{ID: "a2", Scope: "PLACE_SPECIFIC", PlaceID: "p1", Name: "Live Music"}
|
|
|
|
|
// attrCommonCafe := models.Attribute{ID: "a3", Scope: "COMMON", PlaceCategory: "Cafe", Name: "Wifi"}
|
|
|
|
|
// attrSpecificP2 := models.Attribute{ID: "a4", Scope: "PLACE_SPECIFIC", PlaceID: "p2", Name: "Pet Friendly"}
|
|
|
|
|
|
|
|
|
|
// initialDB := &DB{
|
|
|
|
|
// Places: []models.Place{place1, place2},
|
|
|
|
|
// Attributes: []models.Attribute{attrCommonRest, attrSpecificP1, attrCommonCafe, attrSpecificP2},
|
|
|
|
|
// }
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// t.Run("finds common and place-specific attributes for place1", func(t *testing.T) {
|
|
|
|
|
// attrs := store.GetAttributesByPlace(place1)
|
|
|
|
|
// if len(attrs) != 2 {
|
|
|
|
|
// t.Fatalf("GetAttributesByPlace() got %d attributes for place1, want 2", len(attrs))
|
|
|
|
|
// }
|
|
|
|
|
// expected := []models.Attribute{attrCommonRest, attrSpecificP1}
|
|
|
|
|
// if !reflect.DeepEqual(attrs, expected) {
|
|
|
|
|
// t.Errorf("GetAttributesByPlace() got %+v, want %+v", attrs, expected)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("finds common and place-specific attributes for place2", func(t *testing.T) {
|
|
|
|
|
// attrs := store.GetAttributesByPlace(place2)
|
|
|
|
|
// if len(attrs) != 2 {
|
|
|
|
|
// t.Fatalf("GetAttributesByPlace() got %d attributes for place2, want 2", len(attrs))
|
|
|
|
|
// }
|
|
|
|
|
// expected := []models.Attribute{attrCommonCafe, attrSpecificP2}
|
|
|
|
|
// if !reflect.DeepEqual(attrs, expected) {
|
|
|
|
|
// t.Errorf("GetAttributesByPlace() got %+v, want %+v", attrs, expected)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("returns empty slice for place with no relevant attributes", func(t *testing.T) {
|
|
|
|
|
// place3 := models.Place{ID: "p3", Category: "Bar"}
|
|
|
|
|
// attrs := store.GetAttributesByPlace(place3)
|
|
|
|
|
// if len(attrs) != 0 {
|
|
|
|
|
// t.Errorf("GetAttributesByPlace() got %d attributes, want 0 for place3", len(attrs))
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestCreateAttribute(t *testing.T) {
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, nil)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// attr := models.Attribute{ID: "attr1", Name: "Parking", Scope: "COMMON", PlaceCategory: "Restaurant"}
|
|
|
|
|
// err := store.CreateAttribute(attr)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("CreateAttribute() error = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Attributes) != 1 || store.Data.Attributes[0].ID != attr.ID {
|
|
|
|
|
// t.Errorf("CreateAttribute() did not add attribute, got %+v", store.Data.Attributes)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Verify it's saved to disk
|
|
|
|
|
// reloadedStore, err := New(store.path)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// if len(reloadedStore.Data.Attributes) != 1 || reloadedStore.Data.Attributes[0].ID != attr.ID {
|
|
|
|
|
// t.Errorf("CreateAttribute() did not save attribute to disk, got %+v", reloadedStore.Data.Attributes)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestCreateRating(t *testing.T) {
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, nil)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// rating := models.Rating{ID: "r1", PlaceID: "p1", UserID: "u1", Score: 5, Comment: "Great!"}
|
|
|
|
|
// err := store.CreateRating(rating)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("CreateRating() error = %v", err)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if len(store.Data.Ratings) != 1 || store.Data.Ratings[0].ID != rating.ID {
|
|
|
|
|
// t.Errorf("CreateRating() did not add rating, got %+v", store.Data.Ratings)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Verify it's saved to disk
|
|
|
|
|
// reloadedStore, err := New(store.path)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
// }
|
|
|
|
|
// if len(reloadedStore.Data.Ratings) != 1 || reloadedStore.Data.Ratings[0].ID != rating.ID {
|
|
|
|
|
// t.Errorf("CreateRating() did not save rating to disk, got %+v", reloadedStore.Data.Ratings)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// func TestGetRatingByID(t *testing.T) {
|
|
|
|
|
// initialRating := models.Rating{ID: "r1", PlaceID: "p1", UserID: "u1", Score: 5}
|
|
|
|
|
// initialDB := &DB{Ratings: []models.Rating{initialRating}}
|
|
|
|
|
// store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
// defer cleanup()
|
|
|
|
|
|
|
|
|
|
// t.Run("finds existing rating", func(t *testing.T) {
|
|
|
|
|
// foundRating, found := store.GetRatingByID("r1")
|
|
|
|
|
// if !found {
|
|
|
|
|
// t.Fatal("GetRatingByID() did not find existing rating")
|
|
|
|
|
// }
|
|
|
|
|
// if foundRating.ID != initialRating.ID {
|
|
|
|
|
// t.Errorf("GetRatingByID() got rating %+v, want %+v", foundRating, initialRating)
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
// t.Run("does not find non-existent rating", func(t *testing.T) {
|
|
|
|
|
// _, found := store.GetRatingByID("nonexistent")
|
|
|
|
|
// if found {
|
|
|
|
|
// t.Fatal("GetRatingByID() found non-existent rating")
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
func TestUpdateRating(t *testing.T) {
|
|
|
|
|
initialRating := models.Rating{ID: "r1", PlaceID: "p1", UserID: "u1", Score: 5, Comment: "Good"}
|
|
|
|
|
initialDB := &DB{Ratings: map[string]models.Rating{
|
|
|
|
|
initialRating.ID: initialRating}}
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
t.Run("updates existing rating", func(t *testing.T) {
|
|
|
|
|
updatedScore := 4
|
|
|
|
|
updatedComment := "It was okay"
|
|
|
|
|
updatedRating, updated := store.UpdateRating("r1", updatedScore, updatedComment)
|
|
|
|
|
if !updated {
|
|
|
|
|
t.Fatal("UpdateRating() did not update existing rating")
|
|
|
|
|
}
|
|
|
|
|
if updatedRating.Score != updatedScore || updatedRating.Comment != updatedComment {
|
|
|
|
|
t.Errorf("UpdateRating() got score %d, comment %s; want %d, %s", updatedRating.Score, updatedRating.Comment, updatedScore, updatedComment)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify it's saved to disk
|
|
|
|
|
reloadedStore, err := New(store.path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
}
|
|
|
|
|
foundRating, found := reloadedStore.GetRatingByID("r1")
|
|
|
|
|
if !found || foundRating.Score != updatedScore || foundRating.Comment != updatedComment {
|
|
|
|
|
t.Errorf("UpdateRating() did not save updated rating to disk, got %+v", foundRating)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("does not update non-existent rating", func(t *testing.T) {
|
|
|
|
|
_, updated := store.UpdateRating("nonexistent", 1, "bad")
|
|
|
|
|
if updated {
|
|
|
|
|
t.Fatal("UpdateRating() updated non-existent rating")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDeleteRating(t *testing.T) {
|
|
|
|
|
rating1 := models.Rating{ID: "r1", PlaceID: "p1", UserID: "u1", Score: 5}
|
|
|
|
|
rating2 := models.Rating{ID: "r2", PlaceID: "p2", UserID: "u2", Score: 4}
|
|
|
|
|
initialDB := &DB{Ratings: map[string]models.Rating{
|
|
|
|
|
rating1.ID: rating1,
|
|
|
|
|
rating2.ID: rating2,
|
|
|
|
|
}}
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
t.Run("deletes existing rating", func(t *testing.T) {
|
|
|
|
|
deleted := store.DeleteRating("r1")
|
|
|
|
|
if !deleted {
|
|
|
|
|
t.Fatal("DeleteRating() did not delete existing rating")
|
|
|
|
|
}
|
|
|
|
|
if len(store.Data.Ratings) != 1 {
|
|
|
|
|
t.Errorf("DeleteRating() did not remove rating, got %+v", store.Data.Ratings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify it's saved to disk
|
|
|
|
|
reloadedStore, err := New(store.path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
}
|
|
|
|
|
_, found := reloadedStore.GetRatingByID("r1")
|
|
|
|
|
if found {
|
|
|
|
|
t.Error("DeleteRating() did not remove rating from disk")
|
|
|
|
|
}
|
|
|
|
|
if len(reloadedStore.Data.Ratings) != 1 {
|
|
|
|
|
t.Errorf("DeleteRating() disk state incorrect, got %d ratings, want 1", len(reloadedStore.Data.Ratings))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("does not delete non-existent rating", func(t *testing.T) {
|
|
|
|
|
// Re-initialize store for this sub-test to have original data
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
store.DeleteRating("nonexistent")
|
|
|
|
|
if len(store.Data.Ratings) != 2 {
|
|
|
|
|
t.Errorf("DeleteRating() modified ratings for non-existent ID, got %d", len(store.Data.Ratings))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetRatingsByUserID(t *testing.T) {
|
|
|
|
|
rating1 := models.Rating{ID: "r1", PlaceID: "p1", UserID: "u1", Score: 5}
|
|
|
|
|
rating2 := models.Rating{ID: "r2", PlaceID: "p2", UserID: "u1", Score: 4}
|
|
|
|
|
rating3 := models.Rating{ID: "r3", PlaceID: "p1", UserID: "u2", Score: 3}
|
|
|
|
|
initialDB := &DB{Ratings: map[string]models.Rating{
|
|
|
|
|
rating1.ID: rating1,
|
|
|
|
|
rating2.ID: rating2,
|
|
|
|
|
rating3.ID: rating3,
|
|
|
|
|
}}
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
t.Run("finds multiple ratings for a user", func(t *testing.T) {
|
|
|
|
|
ratings := store.GetRatingsByUserID("u1")
|
|
|
|
|
if len(ratings) != 2 {
|
|
|
|
|
t.Fatalf("GetRatingsByUserID() got %d ratings, want 2", len(ratings))
|
|
|
|
|
}
|
|
|
|
|
expected := []models.Rating{rating1, rating2}
|
|
|
|
|
if !reflect.DeepEqual(ratings, expected) {
|
|
|
|
|
t.Errorf("GetRatingsByUserID() got %+v, want %+v", ratings, expected)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("returns empty slice for user with no ratings", func(t *testing.T) {
|
|
|
|
|
ratings := store.GetRatingsByUserID("u3")
|
|
|
|
|
if len(ratings) != 0 {
|
|
|
|
|
t.Errorf("GetRatingsByUserID() got %d ratings, want 0 for u3", len(ratings))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetUserByGoogleID(t *testing.T) {
|
|
|
|
|
initialUser := models.User{ID: "u1", GoogleID: "google1", DisplayName: "Test User 1"}
|
|
|
|
|
initialDB := &DB{Users: map[string]models.User{initialUser.ID: initialUser}}
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
t.Run("finds existing user by Google ID", func(t *testing.T) {
|
|
|
|
|
foundUser, found := store.GetUserByGoogleID("google1")
|
|
|
|
|
if !found {
|
|
|
|
|
t.Fatal("GetUserByGoogleID() did not find existing user")
|
|
|
|
|
}
|
|
|
|
|
if foundUser.ID != initialUser.ID {
|
|
|
|
|
t.Errorf("GetUserByGoogleID() got user %+v, want %+v", foundUser, initialUser)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("does not find non-existent user by Google ID", func(t *testing.T) {
|
|
|
|
|
_, found := store.GetUserByGoogleID("nonexistent_google_id")
|
|
|
|
|
if found {
|
|
|
|
|
t.Fatal("GetUserByGoogleID() found non-existent user")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetUserByID(t *testing.T) {
|
|
|
|
|
initialUser := models.User{ID: "u1", GoogleID: "google1", DisplayName: "Test User 1"}
|
|
|
|
|
initialDB := &DB{Users: map[string]models.User{initialUser.ID: initialUser}}
|
|
|
|
|
store, _, cleanup := setupTestStore(t, initialDB)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
t.Run("finds existing user by ID", func(t *testing.T) {
|
|
|
|
|
foundUser, found := store.GetUserByID("u1")
|
|
|
|
|
if !found {
|
|
|
|
|
t.Fatal("GetUserByID() did not find existing user")
|
|
|
|
|
}
|
|
|
|
|
if foundUser.ID != initialUser.ID {
|
|
|
|
|
t.Errorf("GetUserByID() got user %+v, want %+v", foundUser, initialUser)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("does not find non-existent user by ID", func(t *testing.T) {
|
|
|
|
|
_, found := store.GetUserByID("nonexistent_id")
|
|
|
|
|
if found {
|
|
|
|
|
t.Fatal("GetUserByID() found non-existent user")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateUser(t *testing.T) {
|
|
|
|
|
store, _, cleanup := setupTestStore(t, nil)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
user := models.User{ID: "u1", GoogleID: "google1", DisplayName: "Test User"}
|
|
|
|
|
err := store.CreateUser(user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CreateUser() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := store.Data.Users[user.ID]; len(store.Data.Users) != 1 || !ok {
|
|
|
|
|
t.Errorf("CreateUser() did not add user, got %+v", store.Data.Users)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify it's saved to disk
|
|
|
|
|
reloadedStore, err := New(store.path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to reload store: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, ok := reloadedStore.Data.Users[user.ID]; len(reloadedStore.Data.Users) != 1 || !ok {
|
|
|
|
|
t.Errorf("CreateUser() did not save user to disk, got %+v", reloadedStore.Data.Users)
|
|
|
|
|
}
|
|
|
|
|
}
|