package integration import ( "context" "encoding/json" "fmt" "os" "path/filepath" "testing" "time" "autostore/internal/domain/entities" "autostore/internal/domain/specifications" "autostore/internal/domain/value_objects" "autostore/internal/infrastructure/repositories" "github.com/stretchr/testify/assert" ) const ( ITEM_ID_1 = "00000000-0000-0000-0000-000000000001" ITEM_ID_2 = "00000000-0000-0000-0000-000000000002" ITEM_ID_3 = "00000000-0000-0000-0000-000000000003" EXPIRED_ID = "00000000-0000-0000-0000-000000000004" VALID_ID = "00000000-0000-0000-0000-000000000005" NON_EXISTENT_ID = "00000000-0000-0000-0000-000000000099" ITEM_NAME_1 = "Test Item 1" ITEM_NAME_2 = "Test Item 2" ITEM_NAME_3 = "Test Item 3" EXPIRED_NAME = "Expired Item" VALID_NAME = "Valid Item" ORDER_URL_1 = "http://example.com/order1" ORDER_URL_2 = "http://example.com/order2" ORDER_URL_3 = "http://example.com/order3" EXPIRED_ORDER_URL = "http://example.com/expired-order" VALID_ORDER_URL = "http://example.com/valid-order" USER_ID_1 = "10000000-0000-0000-0000-000000000001" USER_ID_2 = "10000000-0000-0000-0000-000000000002" USER_ID = "10000000-0000-0000-0000-000000000003" MOCKED_NOW = "2023-01-01T12:00:00Z" DATE_FORMAT = time.RFC3339 ) type mockLogger struct { infoLogs []string errorLogs []string } func (m *mockLogger) Info(ctx context.Context, msg string, args ...any) { m.infoLogs = append(m.infoLogs, fmt.Sprintf(msg, args...)) } func (m *mockLogger) Error(ctx context.Context, msg string, args ...any) { m.errorLogs = append(m.errorLogs, fmt.Sprintf(msg, args...)) } func (m *mockLogger) Debug(ctx context.Context, msg string, args ...any) { // Debug implementation - can be empty for tests } func (m *mockLogger) Warn(ctx context.Context, msg string, args ...any) { // Warn implementation - can be empty for tests } func setupTest(t *testing.T) (string, *repositories.FileItemRepository, *mockLogger, func()) { testStoragePath := t.TempDir() logger := &mockLogger{} repo := repositories.NewFileItemRepository(testStoragePath, logger) cleanup := func() { os.RemoveAll(testStoragePath) } return testStoragePath, repo, logger, cleanup } func createTestItem(id, name string, expiration time.Time, orderURL, userID string) *entities.ItemEntity { itemID, _ := value_objects.NewItemID(id) userIDVO, _ := value_objects.NewUserID(userID) expirationVO, _ := value_objects.NewExpirationDate(expiration) item, _ := entities.NewItem(itemID, name, expirationVO, orderURL, userIDVO) return item } func createTestItem1() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2025-01-01T12:00:00Z") return createTestItem(ITEM_ID_1, ITEM_NAME_1, expiration, ORDER_URL_1, USER_ID_1) } func createTestItem2() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2025-01-02T12:00:00Z") return createTestItem(ITEM_ID_2, ITEM_NAME_2, expiration, ORDER_URL_2, USER_ID_2) } func createTestItem3() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2025-01-03T12:00:00Z") return createTestItem(ITEM_ID_3, ITEM_NAME_3, expiration, ORDER_URL_3, USER_ID_1) } func createExpiredItem() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2022-01-01T12:00:00Z") // Past date return createTestItem(EXPIRED_ID, EXPIRED_NAME, expiration, EXPIRED_ORDER_URL, USER_ID) } func createValidItem() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2024-01-01T12:00:00Z") // Future date return createTestItem(VALID_ID, VALID_NAME, expiration, VALID_ORDER_URL, USER_ID) } func createExpiredItemForUser1() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2022-01-01T12:00:00Z") // Past date return createTestItem(ITEM_ID_1, ITEM_NAME_1, expiration, ORDER_URL_1, USER_ID_1) } func createValidItemForUser1() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2024-01-01T12:00:00Z") // Future date return createTestItem(ITEM_ID_2, ITEM_NAME_2, expiration, ORDER_URL_2, USER_ID_1) } func createExpiredItemForUser2() *entities.ItemEntity { expiration, _ := time.Parse(DATE_FORMAT, "2022-01-01T12:00:00Z") // Past date return createTestItem(ITEM_ID_3, ITEM_NAME_3, expiration, ORDER_URL_3, USER_ID_2) } func TestWhenItemIsSavedThenFileIsCreated(t *testing.T) { testStoragePath, repo, _, cleanup := setupTest(t) defer cleanup() item := createTestItem1() err := repo.Save(context.Background(), item) assert.NoError(t, err) filePath := filepath.Join(testStoragePath, "items.json") assert.FileExists(t, filePath) data, err := os.ReadFile(filePath) assert.NoError(t, err) var items map[string]*entities.ItemEntity err = json.Unmarshal(data, &items) assert.NoError(t, err) assert.Len(t, items, 1) savedItem := items[item.GetID().String()] assert.NotNil(t, savedItem) assert.Equal(t, item.GetID().String(), savedItem.GetID().String()) assert.Equal(t, item.GetName(), savedItem.GetName()) assert.Equal(t, item.GetOrderURL(), savedItem.GetOrderURL()) assert.Equal(t, item.GetUserID().String(), savedItem.GetUserID().String()) } func TestWhenItemExistsThenFindByIDReturnsItem(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item := createTestItem1() err := repo.Save(context.Background(), item) assert.NoError(t, err) itemID, _ := value_objects.NewItemID(ITEM_ID_1) foundItem, err := repo.FindByID(context.Background(), itemID) assert.NoError(t, err) assert.NotNil(t, foundItem) assert.Equal(t, item.GetID().String(), foundItem.GetID().String()) assert.Equal(t, item.GetName(), foundItem.GetName()) } func TestWhenItemDoesNotExistThenFindByIDReturnsNil(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() nonExistentID, _ := value_objects.NewItemID(NON_EXISTENT_ID) foundItem, err := repo.FindByID(context.Background(), nonExistentID) assert.Error(t, err) assert.Contains(t, err.Error(), "not found") assert.Nil(t, foundItem) } func TestWhenUserHasMultipleItemsThenFindByUserIDReturnsAllUserItems(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item1 := createTestItem1() item2 := createTestItem2() item3 := createTestItem3() repo.Save(context.Background(), item1) repo.Save(context.Background(), item2) repo.Save(context.Background(), item3) userID, _ := value_objects.NewUserID(USER_ID_1) userItems, err := repo.FindByUserID(context.Background(), userID) assert.NoError(t, err) assert.Len(t, userItems, 2) itemIDs := make([]string, len(userItems)) for i, item := range userItems { itemIDs[i] = item.GetID().String() } assert.Contains(t, itemIDs, ITEM_ID_1) assert.Contains(t, itemIDs, ITEM_ID_3) } func TestWhenItemIsDeletedThenItIsNoLongerFound(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item := createTestItem1() err := repo.Save(context.Background(), item) assert.NoError(t, err) itemID, _ := value_objects.NewItemID(ITEM_ID_1) err = repo.Delete(context.Background(), itemID) assert.NoError(t, err) foundItem, err := repo.FindByID(context.Background(), itemID) assert.Error(t, err) assert.Contains(t, err.Error(), "not found") assert.Nil(t, foundItem) } func TestWhenNonExistentItemIsDeletedThenErrorIsReturned(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() nonExistentID, _ := value_objects.NewItemID(NON_EXISTENT_ID) err := repo.Delete(context.Background(), nonExistentID) assert.Error(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("item with ID %s not found", NON_EXISTENT_ID)) } func TestWhenFilteringByExpirationThenOnlyExpiredItemsAreReturned(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() expiredItem := createExpiredItem() validItem := createValidItem() repo.Save(context.Background(), expiredItem) repo.Save(context.Background(), validItem) now, _ := time.Parse(DATE_FORMAT, MOCKED_NOW) expirationSpec := specifications.NewItemExpirationSpec() spec := expirationSpec.GetSpec(now) filteredItems, err := repo.FindWhere(context.Background(), spec) assert.NoError(t, err) assert.Len(t, filteredItems, 1) assert.Equal(t, expiredItem.GetID().String(), filteredItems[0].GetID().String()) } func TestWhenFilteringByUserIDThenOnlyUserItemsAreReturned(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item1 := createTestItem1() item2 := createTestItem2() item3 := createTestItem3() repo.Save(context.Background(), item1) repo.Save(context.Background(), item2) repo.Save(context.Background(), item3) userID, _ := value_objects.NewUserID(USER_ID_1) spec := specifications.NewSimpleSpecification[*entities.ItemEntity](specifications.Eq("userID", userID)) userItems, err := repo.FindWhere(context.Background(), spec) assert.NoError(t, err) assert.Len(t, userItems, 2) itemIDs := make([]string, len(userItems)) for i, item := range userItems { itemIDs[i] = item.GetID().String() } assert.Contains(t, itemIDs, ITEM_ID_1) assert.Contains(t, itemIDs, ITEM_ID_3) } func TestWhenUsingComplexFilterThenOnlyMatchingItemsAreReturned(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item1 := createExpiredItemForUser1() item2 := createValidItemForUser1() item3 := createExpiredItemForUser2() repo.Save(context.Background(), item1) repo.Save(context.Background(), item2) repo.Save(context.Background(), item3) now, _ := time.Parse(DATE_FORMAT, MOCKED_NOW) userID, _ := value_objects.NewUserID(USER_ID_1) userSpec := specifications.NewSimpleSpecification[*entities.ItemEntity](specifications.Eq("userID", userID)) expirationSpec := specifications.NewItemExpirationSpec() expirationSpecWithTime := expirationSpec.GetSpec(now) complexSpec := userSpec.And(expirationSpecWithTime) filteredItems, err := repo.FindWhere(context.Background(), complexSpec) assert.NoError(t, err) assert.Len(t, filteredItems, 1) assert.Equal(t, item1.GetID().String(), filteredItems[0].GetID().String()) } func TestWhenCheckingExistenceOfExistingItemThenReturnsTrue(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() item := createTestItem1() err := repo.Save(context.Background(), item) assert.NoError(t, err) itemID, _ := value_objects.NewItemID(ITEM_ID_1) exists, err := repo.Exists(context.Background(), itemID) assert.NoError(t, err) assert.True(t, exists) } func TestWhenCheckingExistenceOfNonExistentItemThenReturnsFalse(t *testing.T) { _, repo, _, cleanup := setupTest(t) defer cleanup() nonExistentID, _ := value_objects.NewItemID(NON_EXISTENT_ID) exists, err := repo.Exists(context.Background(), nonExistentID) assert.NoError(t, err) assert.False(t, exists) } func TestWhenDifferentRepositoryInstancesShareSameFile(t *testing.T) { testStoragePath, _, _, cleanup := setupTest(t) defer cleanup() logger := &mockLogger{} repo1 := repositories.NewFileItemRepository(testStoragePath, logger) repo2 := repositories.NewFileItemRepository(testStoragePath, logger) item := createTestItem1() err := repo1.Save(context.Background(), item) assert.NoError(t, err) itemID, _ := value_objects.NewItemID(ITEM_ID_1) foundItem, err := repo2.FindByID(context.Background(), itemID) assert.NoError(t, err) assert.NotNil(t, foundItem) assert.Equal(t, item.GetID().String(), foundItem.GetID().String()) assert.Equal(t, item.GetName(), foundItem.GetName()) assert.Equal(t, item.GetOrderURL(), foundItem.GetOrderURL()) assert.Equal(t, item.GetUserID().String(), foundItem.GetUserID().String()) }