refactor(favorites): simplify usecase to tags
- commit
- 45b8a06f5d50e852cf6fc20c79f80958ad914cd8
- author
- brice · · 2026-06-28 07:32 UTC
- parent
- 8077f20
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +60 -267
M cmd/dumber/main.go +1 -4
| @@ -691,7 +691,6 @@ func setupSignalHandler(ctx context.Context, app *ui.App) { | |||
| 691 | 691 | type repositories struct { | |
| 692 | 692 | history repository.HistoryRepository | |
| 693 | 693 | favorite repository.FavoriteRepository | |
| 694 | - | folder repository.FolderRepository | |
| 695 | 694 | tag repository.TagRepository | |
| 696 | 695 | zoom repository.ZoomRepository | |
| 697 | 696 | permission port.PermissionRepository | |
| @@ -704,7 +703,6 @@ func createRepositories(db *sql.DB) *repositories { | |||
| 704 | 703 | return &repositories{ | |
| 705 | 704 | history: sqlite.NewHistoryRepository(db), | |
| 706 | 705 | favorite: sqlite.NewFavoriteRepository(db), | |
| 707 | - | folder: sqlite.NewFolderRepository(db), | |
| 708 | 706 | tag: sqlite.NewTagRepository(db), | |
| 709 | 707 | zoom: sqlite.NewZoomRepository(db), | |
| 710 | 708 | permission: sqlite.NewPermissionRepository(db), | |
| @@ -718,7 +716,6 @@ func createLazyRepositories(provider port.DatabaseProvider) *repositories { | |||
| 718 | 716 | return &repositories{ | |
| 719 | 717 | history: sqlite.NewLazyHistoryRepository(provider), | |
| 720 | 718 | favorite: sqlite.NewLazyFavoriteRepository(provider), | |
| 721 | - | folder: sqlite.NewLazyFolderRepository(provider), | |
| 722 | 719 | tag: sqlite.NewLazyTagRepository(provider), | |
| 723 | 720 | zoom: sqlite.NewLazyZoomRepository(provider), | |
| 724 | 721 | permission: sqlite.NewLazyPermissionRepository(provider), | |
| @@ -804,7 +801,7 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||
| 804 | 801 | tabs: usecase.NewManageTabsUseCase(idGenerator, localPaths), | |
| 805 | 802 | panes: usecase.NewManagePanesUseCase(idGenerator, localPaths), | |
| 806 | 803 | history: historyUC, | |
| 807 | - | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.folder, repos.tag), | |
| 804 | + | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.tag), | |
| 808 | 805 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | |
| 809 | 806 | permission: permissionUC, | |
| 810 | 807 | navigate: usecase.NewNavigateUseCase(defaultZoom), | |
| @@ -691,7 +691,6 @@ func setupSignalHandler(ctx context.Context, app *ui.App) { | |||||
| 691 | type repositories struct { | 691 | type repositories struct { | ||
| 692 | history repository.HistoryRepository | 692 | history repository.HistoryRepository | ||
| 693 | favorite repository.FavoriteRepository | 693 | favorite repository.FavoriteRepository | ||
| 694 | - | folder repository.FolderRepository | |||
| 695 | tag repository.TagRepository | 694 | tag repository.TagRepository | ||
| 696 | zoom repository.ZoomRepository | 695 | zoom repository.ZoomRepository | ||
| 697 | permission port.PermissionRepository | 696 | permission port.PermissionRepository | ||
| @@ -704,7 +703,6 @@ func createRepositories(db *sql.DB) *repositories { | |||||
| 704 | return &repositories{ | 703 | return &repositories{ | ||
| 705 | history: sqlite.NewHistoryRepository(db), | 704 | history: sqlite.NewHistoryRepository(db), | ||
| 706 | favorite: sqlite.NewFavoriteRepository(db), | 705 | favorite: sqlite.NewFavoriteRepository(db), | ||
| 707 | - | folder: sqlite.NewFolderRepository(db), | |||
| 708 | tag: sqlite.NewTagRepository(db), | 706 | tag: sqlite.NewTagRepository(db), | ||
| 709 | zoom: sqlite.NewZoomRepository(db), | 707 | zoom: sqlite.NewZoomRepository(db), | ||
| 710 | permission: sqlite.NewPermissionRepository(db), | 708 | permission: sqlite.NewPermissionRepository(db), | ||
| @@ -718,7 +716,6 @@ func createLazyRepositories(provider port.DatabaseProvider) *repositories { | |||||
| 718 | return &repositories{ | 716 | return &repositories{ | ||
| 719 | history: sqlite.NewLazyHistoryRepository(provider), | 717 | history: sqlite.NewLazyHistoryRepository(provider), | ||
| 720 | favorite: sqlite.NewLazyFavoriteRepository(provider), | 718 | favorite: sqlite.NewLazyFavoriteRepository(provider), | ||
| 721 | - | folder: sqlite.NewLazyFolderRepository(provider), | |||
| 722 | tag: sqlite.NewLazyTagRepository(provider), | 719 | tag: sqlite.NewLazyTagRepository(provider), | ||
| 723 | zoom: sqlite.NewLazyZoomRepository(provider), | 720 | zoom: sqlite.NewLazyZoomRepository(provider), | ||
| 724 | permission: sqlite.NewLazyPermissionRepository(provider), | 721 | permission: sqlite.NewLazyPermissionRepository(provider), | ||
| @@ -804,7 +801,7 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases { | |||||
| 804 | tabs: usecase.NewManageTabsUseCase(idGenerator, localPaths), | 801 | tabs: usecase.NewManageTabsUseCase(idGenerator, localPaths), | ||
| 805 | panes: usecase.NewManagePanesUseCase(idGenerator, localPaths), | 802 | panes: usecase.NewManagePanesUseCase(idGenerator, localPaths), | ||
| 806 | history: historyUC, | 803 | history: historyUC, | ||
| 807 | - | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.folder, repos.tag), | 804 | + | favorites: usecase.NewManageFavoritesUseCase(repos.favorite, repos.tag), |
| 808 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | 805 | zoom: usecase.NewManageZoomUseCase(repos.zoom, defaultZoom, zoomCache), | ||
| 809 | permission: permissionUC, | 806 | permission: permissionUC, | ||
| 810 | navigate: usecase.NewNavigateUseCase(defaultZoom), | 807 | navigate: usecase.NewNavigateUseCase(defaultZoom), | ||
M internal/application/usecase/manage_favorites.go +11 -185
| @@ -20,10 +20,9 @@ import ( | |||
| 20 | 20 | // Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites. | |
| 21 | 21 | var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil) | |
| 22 | 22 | ||
| 23 | - | // ManageFavoritesUseCase handles favorite, folder, and tag operations. | |
| 23 | + | // ManageFavoritesUseCase handles favorite and tag operations. | |
| 24 | 24 | type ManageFavoritesUseCase struct { | |
| 25 | 25 | favoriteRepo repository.FavoriteRepository | |
| 26 | - | folderRepo repository.FolderRepository | |
| 27 | 26 | tagRepo repository.TagRepository | |
| 28 | 27 | ||
| 29 | 28 | cacheMu sync.Mutex | |
| @@ -37,12 +36,10 @@ const favoritesCacheTTL = 2 * time.Second | |||
| 37 | 36 | // NewManageFavoritesUseCase creates a new favorites management use case. | |
| 38 | 37 | func NewManageFavoritesUseCase( | |
| 39 | 38 | favoriteRepo repository.FavoriteRepository, | |
| 40 | - | folderRepo repository.FolderRepository, | |
| 41 | 39 | tagRepo repository.TagRepository, | |
| 42 | 40 | ) *ManageFavoritesUseCase { | |
| 43 | 41 | return &ManageFavoritesUseCase{ | |
| 44 | 42 | favoriteRepo: favoriteRepo, | |
| 45 | - | folderRepo: folderRepo, | |
| 46 | 43 | tagRepo: tagRepo, | |
| 47 | 44 | } | |
| 48 | 45 | } | |
| @@ -52,7 +49,6 @@ type AddFavoriteInput struct { | |||
| 52 | 49 | URL string | |
| 53 | 50 | Title string | |
| 54 | 51 | FaviconURL string | |
| 55 | - | FolderID *entity.FolderID | |
| 56 | 52 | Tags []entity.TagID | |
| 57 | 53 | } | |
| 58 | 54 | ||
| @@ -78,7 +74,6 @@ func (uc *ManageFavoritesUseCase) addNewFavorite(ctx context.Context, input AddF | |||
| 78 | 74 | log := logging.FromContext(ctx) | |
| 79 | 75 | fav := entity.NewFavorite(input.URL, input.Title) | |
| 80 | 76 | fav.FaviconURL = input.FaviconURL | |
| 81 | - | fav.FolderID = input.FolderID | |
| 82 | 77 | ||
| 83 | 78 | if err := uc.favoriteRepo.Save(ctx, fav); err != nil { | |
| 84 | 79 | return nil, fmt.Errorf("failed to save favorite: %w", err) | |
| @@ -125,7 +120,6 @@ func (uc *ManageFavoritesUseCase) AddFavorite(ctx context.Context, input dto.Fav | |||
| 125 | 120 | URL: favoriteURL, | |
| 126 | 121 | Title: title, | |
| 127 | 122 | FaviconURL: strings.TrimSpace(input.FaviconURL), | |
| 128 | - | FolderID: input.FolderID, | |
| 129 | 123 | Tags: tags, | |
| 130 | 124 | }) | |
| 131 | 125 | } | |
| @@ -204,7 +198,6 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||
| 204 | 198 | } | |
| 205 | 199 | fav.Title = title | |
| 206 | 200 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | |
| 207 | - | fav.FolderID = input.FolderID | |
| 208 | 201 | fav.ShortcutKey = input.ShortcutKey | |
| 209 | 202 | ||
| 210 | 203 | if err := uc.Update(ctx, fav); err != nil { | |
| @@ -263,22 +256,6 @@ func (uc *ManageFavoritesUseCase) Update(ctx context.Context, fav *entity.Favori | |||
| 263 | 256 | return nil | |
| 264 | 257 | } | |
| 265 | 258 | ||
| 266 | - | // Move changes a favorite's folder. | |
| 267 | - | func (uc *ManageFavoritesUseCase) Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error { | |
| 268 | - | log := logging.FromContext(ctx) | |
| 269 | - | log.Debug(). | |
| 270 | - | Int64("favorite_id", int64(id)). | |
| 271 | - | Msg("moving favorite to folder") | |
| 272 | - | ||
| 273 | - | if err := uc.favoriteRepo.SetFolder(ctx, id, folderID); err != nil { | |
| 274 | - | return fmt.Errorf("failed to move favorite: %w", err) | |
| 275 | - | } | |
| 276 | - | ||
| 277 | - | log.Info().Int64("id", int64(id)).Msg("favorite moved") | |
| 278 | - | uc.invalidateCache() | |
| 279 | - | return nil | |
| 280 | - | } | |
| 281 | - | ||
| 282 | 259 | // SetShortcut assigns or removes a keyboard shortcut (1-9). | |
| 283 | 260 | func (uc *ManageFavoritesUseCase) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error { | |
| 284 | 261 | log := logging.FromContext(ctx) | |
| @@ -536,141 +513,13 @@ func (uc *ManageFavoritesUseCase) invalidateCache() { | |||
| 536 | 513 | uc.cacheTime = time.Time{} | |
| 537 | 514 | } | |
| 538 | 515 | ||
| 539 | - | // GetTree builds a complete hierarchical view of folders and favorites. | |
| 540 | - | func (uc *ManageFavoritesUseCase) GetTree(ctx context.Context) (*entity.FavoriteTree, error) { | |
| 541 | - | log := logging.FromContext(ctx) | |
| 542 | - | log.Debug().Msg("building favorite tree") | |
| 543 | - | ||
| 544 | - | tree := entity.NewFavoriteTree() | |
| 545 | - | ||
| 546 | - | // Get all folders | |
| 547 | - | folders, err := uc.folderRepo.GetAll(ctx) | |
| 548 | - | if err != nil { | |
| 549 | - | return nil, fmt.Errorf("failed to get folders: %w", err) | |
| 550 | - | } | |
| 551 | - | ||
| 552 | - | // Build folder map and organize by parent | |
| 553 | - | for _, folder := range folders { | |
| 554 | - | tree.FolderMap[folder.ID] = folder | |
| 555 | - | if folder.ParentID == nil { | |
| 556 | - | tree.RootFolders = append(tree.RootFolders, folder) | |
| 557 | - | } else { | |
| 558 | - | tree.ChildFolders[*folder.ParentID] = append(tree.ChildFolders[*folder.ParentID], folder) | |
| 559 | - | } | |
| 560 | - | } | |
| 561 | - | ||
| 562 | - | // Get all favorites | |
| 563 | - | favorites, err := uc.favoriteRepo.GetAll(ctx) | |
| 564 | - | if err != nil { | |
| 565 | - | return nil, fmt.Errorf("failed to get favorites: %w", err) | |
| 566 | - | } | |
| 567 | - | ||
| 568 | - | // Organize favorites by folder | |
| 569 | - | for _, fav := range favorites { | |
| 570 | - | if fav.FolderID == nil { | |
| 571 | - | tree.RootFavorites = append(tree.RootFavorites, fav) | |
| 572 | - | } else { | |
| 573 | - | tree.ChildFavorites[*fav.FolderID] = append(tree.ChildFavorites[*fav.FolderID], fav) | |
| 574 | - | } | |
| 575 | - | } | |
| 576 | - | ||
| 577 | - | log.Debug(). | |
| 578 | - | Int("folders", len(folders)). | |
| 579 | - | Int("favorites", len(favorites)). | |
| 580 | - | Msg("favorite tree built") | |
| 581 | - | ||
| 582 | - | return tree, nil | |
| 583 | - | } | |
| 584 | - | ||
| 585 | - | // CreateFolder creates a new folder. | |
| 586 | - | func (uc *ManageFavoritesUseCase) CreateFolder(ctx context.Context, name, icon string, parentID *entity.FolderID) (*entity.Folder, error) { | |
| 516 | + | // AddTag creates a new tag. | |
| 517 | + | func (uc *ManageFavoritesUseCase) AddTag(ctx context.Context, name, color string) (*entity.Tag, error) { | |
| 587 | 518 | name = strings.TrimSpace(name) | |
| 588 | - | icon = strings.TrimSpace(icon) | |
| 519 | + | color = strings.TrimSpace(color) | |
| 589 | 520 | if name == "" { | |
| 590 | - | return nil, fmt.Errorf("folder name is required") | |
| 591 | - | } | |
| 592 | - | log := logging.FromContext(ctx) | |
| 593 | - | log.Debug().Str("name", name).Msg("creating folder") | |
| 594 | - | ||
| 595 | - | folder := entity.NewFolder(name) | |
| 596 | - | folder.Icon = icon | |
| 597 | - | folder.ParentID = parentID | |
| 598 | - | ||
| 599 | - | if err := uc.folderRepo.Save(ctx, folder); err != nil { | |
| 600 | - | return nil, fmt.Errorf("failed to create folder: %w", err) | |
| 601 | - | } | |
| 602 | - | ||
| 603 | - | log.Info().Str("name", name).Int64("id", int64(folder.ID)).Msg("folder created") | |
| 604 | - | return folder, nil | |
| 605 | - | } | |
| 606 | - | ||
| 607 | - | // DeleteFolder removes a folder. Favorites in the folder are moved to root. | |
| 608 | - | func (uc *ManageFavoritesUseCase) DeleteFolder(ctx context.Context, id entity.FolderID) error { | |
| 609 | - | log := logging.FromContext(ctx) | |
| 610 | - | log.Debug().Int64("id", int64(id)).Msg("deleting folder") | |
| 611 | - | ||
| 612 | - | // Move favorites from this folder to root (or parent) | |
| 613 | - | folder, err := uc.folderRepo.FindByID(ctx, id) | |
| 614 | - | if err != nil { | |
| 615 | - | return fmt.Errorf("failed to find folder: %w", err) | |
| 616 | - | } | |
| 617 | - | if folder == nil { | |
| 618 | - | log.Debug().Int64("id", int64(id)).Msg("folder not found") | |
| 619 | - | return nil | |
| 620 | - | } | |
| 621 | - | ||
| 622 | - | // Get favorites in this folder | |
| 623 | - | favorites, err := uc.favoriteRepo.GetByFolder(ctx, &id) | |
| 624 | - | if err != nil { | |
| 625 | - | return fmt.Errorf("failed to get folder favorites: %w", err) | |
| 626 | - | } | |
| 627 | - | ||
| 628 | - | // Move favorites to parent folder (or root) | |
| 629 | - | for _, fav := range favorites { | |
| 630 | - | if setErr := uc.favoriteRepo.SetFolder(ctx, fav.ID, folder.ParentID); setErr != nil { | |
| 631 | - | log.Warn(). | |
| 632 | - | Int64("favorite_id", int64(fav.ID)). | |
| 633 | - | Err(setErr). | |
| 634 | - | Msg("failed to move favorite from deleted folder") | |
| 635 | - | } | |
| 636 | - | } | |
| 637 | - | uc.invalidateCache() | |
| 638 | - | ||
| 639 | - | // Get child folders | |
| 640 | - | children, err := uc.folderRepo.GetChildren(ctx, &id) | |
| 641 | - | if err != nil { | |
| 642 | - | return fmt.Errorf("failed to get child folders: %w", err) | |
| 521 | + | return nil, fmt.Errorf("tag name is required") | |
| 643 | 522 | } | |
| 644 | - | ||
| 645 | - | // Recursively delete child folders | |
| 646 | - | for _, child := range children { | |
| 647 | - | if err := uc.DeleteFolder(ctx, child.ID); err != nil { | |
| 648 | - | log.Warn(). | |
| 649 | - | Int64("child_id", int64(child.ID)). | |
| 650 | - | Err(err). | |
| 651 | - | Msg("failed to delete child folder") | |
| 652 | - | } | |
| 653 | - | } | |
| 654 | - | ||
| 655 | - | // Delete the folder itself | |
| 656 | - | if err := uc.folderRepo.Delete(ctx, id); err != nil { | |
| 657 | - | return fmt.Errorf("failed to delete folder: %w", err) | |
| 658 | - | } | |
| 659 | - | ||
| 660 | - | log.Info().Int64("id", int64(id)).Msg("folder deleted") | |
| 661 | - | return nil | |
| 662 | - | } | |
| 663 | - | ||
| 664 | - | // GetAllFolders retrieves all folders. | |
| 665 | - | func (uc *ManageFavoritesUseCase) GetAllFolders(ctx context.Context) ([]*entity.Folder, error) { | |
| 666 | - | log := logging.FromContext(ctx) | |
| 667 | - | log.Debug().Msg("getting all folders") | |
| 668 | - | ||
| 669 | - | return uc.folderRepo.GetAll(ctx) | |
| 670 | - | } | |
| 671 | - | ||
| 672 | - | // AddTag creates a new tag. | |
| 673 | - | func (uc *ManageFavoritesUseCase) AddTag(ctx context.Context, name, color string) (*entity.Tag, error) { | |
| 674 | 523 | log := logging.FromContext(ctx) | |
| 675 | 524 | log.Debug().Str("name", name).Str("color", color).Msg("creating tag") | |
| 676 | 525 | ||
| @@ -759,34 +608,13 @@ func (uc *ManageFavoritesUseCase) GetTagsForFavorite(ctx context.Context, favID | |||
| 759 | 608 | return uc.tagRepo.GetForFavorite(ctx, favID) | |
| 760 | 609 | } | |
| 761 | 610 | ||
| 762 | - | // UpdateFolder updates a folder's name and icon. | |
| 763 | - | func (uc *ManageFavoritesUseCase) UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error { | |
| 764 | - | log := logging.FromContext(ctx) | |
| 765 | - | log.Debug().Int64("id", int64(id)).Str("name", name).Msg("updating folder") | |
| 766 | - | ||
| 767 | - | folder, err := uc.folderRepo.FindByID(ctx, id) | |
| 768 | - | if err != nil { | |
| 769 | - | return fmt.Errorf("failed to find folder: %w", err) | |
| 770 | - | } | |
| 771 | - | if folder == nil { | |
| 772 | - | return fmt.Errorf("folder %d not found", id) | |
| 773 | - | } | |
| 774 | - | ||
| 775 | - | if name != "" { | |
| 776 | - | folder.Name = name | |
| 777 | - | } | |
| 778 | - | folder.Icon = icon | |
| 779 | - | ||
| 780 | - | if err := uc.folderRepo.Save(ctx, folder); err != nil { | |
| 781 | - | return fmt.Errorf("failed to update folder: %w", err) | |
| 782 | - | } | |
| 783 | - | ||
| 784 | - | log.Info().Int64("id", int64(id)).Msg("folder updated") | |
| 785 | - | return nil | |
| 786 | - | } | |
| 787 | - | ||
| 788 | 611 | // UpdateTag updates a tag's name and color. | |
| 789 | 612 | func (uc *ManageFavoritesUseCase) UpdateTag(ctx context.Context, id entity.TagID, name, color string) error { | |
| 613 | + | name = strings.TrimSpace(name) | |
| 614 | + | color = strings.TrimSpace(color) | |
| 615 | + | if name == "" { | |
| 616 | + | return fmt.Errorf("tag name is required") | |
| 617 | + | } | |
| 790 | 618 | log := logging.FromContext(ctx) | |
| 791 | 619 | log.Debug().Int64("id", int64(id)).Str("name", name).Str("color", color).Msg("updating tag") | |
| 792 | 620 | ||
| @@ -798,9 +626,7 @@ func (uc *ManageFavoritesUseCase) UpdateTag(ctx context.Context, id entity.TagID | |||
| 798 | 626 | return fmt.Errorf("tag %d not found", id) | |
| 799 | 627 | } | |
| 800 | 628 | ||
| 801 | - | if name != "" { | |
| 802 | - | tag.Name = name | |
| 803 | - | } | |
| 629 | + | tag.Name = name | |
| 804 | 630 | if color != "" { | |
| 805 | 631 | tag.Color = color | |
| 806 | 632 | } | |
| @@ -20,10 +20,9 @@ import ( | |||||
| 20 | // Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites. | 20 | // Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites. | ||
| 21 | var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil) | 21 | var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil) | ||
| 22 | 22 | ||||
| 23 | - | // ManageFavoritesUseCase handles favorite, folder, and tag operations. | 23 | + | // ManageFavoritesUseCase handles favorite and tag operations. |
| 24 | type ManageFavoritesUseCase struct { | 24 | type ManageFavoritesUseCase struct { | ||
| 25 | favoriteRepo repository.FavoriteRepository | 25 | favoriteRepo repository.FavoriteRepository | ||
| 26 | - | folderRepo repository.FolderRepository | |||
| 27 | tagRepo repository.TagRepository | 26 | tagRepo repository.TagRepository | ||
| 28 | 27 | ||||
| 29 | cacheMu sync.Mutex | 28 | cacheMu sync.Mutex | ||
| @@ -37,12 +36,10 @@ const favoritesCacheTTL = 2 * time.Second | |||||
| 37 | // NewManageFavoritesUseCase creates a new favorites management use case. | 36 | // NewManageFavoritesUseCase creates a new favorites management use case. | ||
| 38 | func NewManageFavoritesUseCase( | 37 | func NewManageFavoritesUseCase( | ||
| 39 | favoriteRepo repository.FavoriteRepository, | 38 | favoriteRepo repository.FavoriteRepository, | ||
| 40 | - | folderRepo repository.FolderRepository, | |||
| 41 | tagRepo repository.TagRepository, | 39 | tagRepo repository.TagRepository, | ||
| 42 | ) *ManageFavoritesUseCase { | 40 | ) *ManageFavoritesUseCase { | ||
| 43 | return &ManageFavoritesUseCase{ | 41 | return &ManageFavoritesUseCase{ | ||
| 44 | favoriteRepo: favoriteRepo, | 42 | favoriteRepo: favoriteRepo, | ||
| 45 | - | folderRepo: folderRepo, | |||
| 46 | tagRepo: tagRepo, | 43 | tagRepo: tagRepo, | ||
| 47 | } | 44 | } | ||
| 48 | } | 45 | } | ||
| @@ -52,7 +49,6 @@ type AddFavoriteInput struct { | |||||
| 52 | URL string | 49 | URL string | ||
| 53 | Title string | 50 | Title string | ||
| 54 | FaviconURL string | 51 | FaviconURL string | ||
| 55 | - | FolderID *entity.FolderID | |||
| 56 | Tags []entity.TagID | 52 | Tags []entity.TagID | ||
| 57 | } | 53 | } | ||
| 58 | 54 | ||||
| @@ -78,7 +74,6 @@ func (uc *ManageFavoritesUseCase) addNewFavorite(ctx context.Context, input AddF | |||||
| 78 | log := logging.FromContext(ctx) | 74 | log := logging.FromContext(ctx) | ||
| 79 | fav := entity.NewFavorite(input.URL, input.Title) | 75 | fav := entity.NewFavorite(input.URL, input.Title) | ||
| 80 | fav.FaviconURL = input.FaviconURL | 76 | fav.FaviconURL = input.FaviconURL | ||
| 81 | - | fav.FolderID = input.FolderID | |||
| 82 | 77 | ||||
| 83 | if err := uc.favoriteRepo.Save(ctx, fav); err != nil { | 78 | if err := uc.favoriteRepo.Save(ctx, fav); err != nil { | ||
| 84 | return nil, fmt.Errorf("failed to save favorite: %w", err) | 79 | return nil, fmt.Errorf("failed to save favorite: %w", err) | ||
| @@ -125,7 +120,6 @@ func (uc *ManageFavoritesUseCase) AddFavorite(ctx context.Context, input dto.Fav | |||||
| 125 | URL: favoriteURL, | 120 | URL: favoriteURL, | ||
| 126 | Title: title, | 121 | Title: title, | ||
| 127 | FaviconURL: strings.TrimSpace(input.FaviconURL), | 122 | FaviconURL: strings.TrimSpace(input.FaviconURL), | ||
| 128 | - | FolderID: input.FolderID, | |||
| 129 | Tags: tags, | 123 | Tags: tags, | ||
| 130 | }) | 124 | }) | ||
| 131 | } | 125 | } | ||
| @@ -204,7 +198,6 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||||
| 204 | } | 198 | } | ||
| 205 | fav.Title = title | 199 | fav.Title = title | ||
| 206 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | 200 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | ||
| 207 | - | fav.FolderID = input.FolderID | |||
| 208 | fav.ShortcutKey = input.ShortcutKey | 201 | fav.ShortcutKey = input.ShortcutKey | ||
| 209 | 202 | ||||
| 210 | if err := uc.Update(ctx, fav); err != nil { | 203 | if err := uc.Update(ctx, fav); err != nil { | ||
| @@ -263,22 +256,6 @@ func (uc *ManageFavoritesUseCase) Update(ctx context.Context, fav *entity.Favori | |||||
| 263 | return nil | 256 | return nil | ||
| 264 | } | 257 | } | ||
| 265 | 258 | ||||
| 266 | - | // Move changes a favorite's folder. | |||
| 267 | - | func (uc *ManageFavoritesUseCase) Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error { | |||
| 268 | - | log := logging.FromContext(ctx) | |||
| 269 | - | log.Debug(). | |||
| 270 | - | Int64("favorite_id", int64(id)). | |||
| 271 | - | Msg("moving favorite to folder") | |||
| 272 | - | ||||
| 273 | - | if err := uc.favoriteRepo.SetFolder(ctx, id, folderID); err != nil { | |||
| 274 | - | return fmt.Errorf("failed to move favorite: %w", err) | |||
| 275 | - | } | |||
| 276 | - | ||||
| 277 | - | log.Info().Int64("id", int64(id)).Msg("favorite moved") | |||
| 278 | - | uc.invalidateCache() | |||
| 279 | - | return nil | |||
| 280 | - | } | |||
| 281 | - | ||||
| 282 | // SetShortcut assigns or removes a keyboard shortcut (1-9). | 259 | // SetShortcut assigns or removes a keyboard shortcut (1-9). | ||
| 283 | func (uc *ManageFavoritesUseCase) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error { | 260 | func (uc *ManageFavoritesUseCase) SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error { | ||
| 284 | log := logging.FromContext(ctx) | 261 | log := logging.FromContext(ctx) | ||
| @@ -536,141 +513,13 @@ func (uc *ManageFavoritesUseCase) invalidateCache() { | |||||
| 536 | uc.cacheTime = time.Time{} | 513 | uc.cacheTime = time.Time{} | ||
| 537 | } | 514 | } | ||
| 538 | 515 | ||||
| 539 | - | // GetTree builds a complete hierarchical view of folders and favorites. | 516 | + | // AddTag creates a new tag. |
| 540 | - | func (uc *ManageFavoritesUseCase) GetTree(ctx context.Context) (*entity.FavoriteTree, error) { | 517 | + | func (uc *ManageFavoritesUseCase) AddTag(ctx context.Context, name, color string) (*entity.Tag, error) { |
| 541 | - | log := logging.FromContext(ctx) | |||
| 542 | - | log.Debug().Msg("building favorite tree") | |||
| 543 | - | ||||
| 544 | - | tree := entity.NewFavoriteTree() | |||
| 545 | - | ||||
| 546 | - | // Get all folders | |||
| 547 | - | folders, err := uc.folderRepo.GetAll(ctx) | |||
| 548 | - | if err != nil { | |||
| 549 | - | return nil, fmt.Errorf("failed to get folders: %w", err) | |||
| 550 | - | } | |||
| 551 | - | ||||
| 552 | - | // Build folder map and organize by parent | |||
| 553 | - | for _, folder := range folders { | |||
| 554 | - | tree.FolderMap[folder.ID] = folder | |||
| 555 | - | if folder.ParentID == nil { | |||
| 556 | - | tree.RootFolders = append(tree.RootFolders, folder) | |||
| 557 | - | } else { | |||
| 558 | - | tree.ChildFolders[*folder.ParentID] = append(tree.ChildFolders[*folder.ParentID], folder) | |||
| 559 | - | } | |||
| 560 | - | } | |||
| 561 | - | ||||
| 562 | - | // Get all favorites | |||
| 563 | - | favorites, err := uc.favoriteRepo.GetAll(ctx) | |||
| 564 | - | if err != nil { | |||
| 565 | - | return nil, fmt.Errorf("failed to get favorites: %w", err) | |||
| 566 | - | } | |||
| 567 | - | ||||
| 568 | - | // Organize favorites by folder | |||
| 569 | - | for _, fav := range favorites { | |||
| 570 | - | if fav.FolderID == nil { | |||
| 571 | - | tree.RootFavorites = append(tree.RootFavorites, fav) | |||
| 572 | - | } else { | |||
| 573 | - | tree.ChildFavorites[*fav.FolderID] = append(tree.ChildFavorites[*fav.FolderID], fav) | |||
| 574 | - | } | |||
| 575 | - | } | |||
| 576 | - | ||||
| 577 | - | log.Debug(). | |||
| 578 | - | Int("folders", len(folders)). | |||
| 579 | - | Int("favorites", len(favorites)). | |||
| 580 | - | Msg("favorite tree built") | |||
| 581 | - | ||||
| 582 | - | return tree, nil | |||
| 583 | - | } | |||
| 584 | - | ||||
| 585 | - | // CreateFolder creates a new folder. | |||
| 586 | - | func (uc *ManageFavoritesUseCase) CreateFolder(ctx context.Context, name, icon string, parentID *entity.FolderID) (*entity.Folder, error) { | |||
| 587 | name = strings.TrimSpace(name) | 518 | name = strings.TrimSpace(name) | ||
| 588 | - | icon = strings.TrimSpace(icon) | 519 | + | color = strings.TrimSpace(color) |
| 589 | if name == "" { | 520 | if name == "" { | ||
| 590 | - | return nil, fmt.Errorf("folder name is required") | 521 | + | return nil, fmt.Errorf("tag name is required") |
| 591 | - | } | |||
| 592 | - | log := logging.FromContext(ctx) | |||
| 593 | - | log.Debug().Str("name", name).Msg("creating folder") | |||
| 594 | - | ||||
| 595 | - | folder := entity.NewFolder(name) | |||
| 596 | - | folder.Icon = icon | |||
| 597 | - | folder.ParentID = parentID | |||
| 598 | - | ||||
| 599 | - | if err := uc.folderRepo.Save(ctx, folder); err != nil { | |||
| 600 | - | return nil, fmt.Errorf("failed to create folder: %w", err) | |||
| 601 | - | } | |||
| 602 | - | ||||
| 603 | - | log.Info().Str("name", name).Int64("id", int64(folder.ID)).Msg("folder created") | |||
| 604 | - | return folder, nil | |||
| 605 | - | } | |||
| 606 | - | ||||
| 607 | - | // DeleteFolder removes a folder. Favorites in the folder are moved to root. | |||
| 608 | - | func (uc *ManageFavoritesUseCase) DeleteFolder(ctx context.Context, id entity.FolderID) error { | |||
| 609 | - | log := logging.FromContext(ctx) | |||
| 610 | - | log.Debug().Int64("id", int64(id)).Msg("deleting folder") | |||
| 611 | - | ||||
| 612 | - | // Move favorites from this folder to root (or parent) | |||
| 613 | - | folder, err := uc.folderRepo.FindByID(ctx, id) | |||
| 614 | - | if err != nil { | |||
| 615 | - | return fmt.Errorf("failed to find folder: %w", err) | |||
| 616 | - | } | |||
| 617 | - | if folder == nil { | |||
| 618 | - | log.Debug().Int64("id", int64(id)).Msg("folder not found") | |||
| 619 | - | return nil | |||
| 620 | - | } | |||
| 621 | - | ||||
| 622 | - | // Get favorites in this folder | |||
| 623 | - | favorites, err := uc.favoriteRepo.GetByFolder(ctx, &id) | |||
| 624 | - | if err != nil { | |||
| 625 | - | return fmt.Errorf("failed to get folder favorites: %w", err) | |||
| 626 | - | } | |||
| 627 | - | ||||
| 628 | - | // Move favorites to parent folder (or root) | |||
| 629 | - | for _, fav := range favorites { | |||
| 630 | - | if setErr := uc.favoriteRepo.SetFolder(ctx, fav.ID, folder.ParentID); setErr != nil { | |||
| 631 | - | log.Warn(). | |||
| 632 | - | Int64("favorite_id", int64(fav.ID)). | |||
| 633 | - | Err(setErr). | |||
| 634 | - | Msg("failed to move favorite from deleted folder") | |||
| 635 | - | } | |||
| 636 | - | } | |||
| 637 | - | uc.invalidateCache() | |||
| 638 | - | ||||
| 639 | - | // Get child folders | |||
| 640 | - | children, err := uc.folderRepo.GetChildren(ctx, &id) | |||
| 641 | - | if err != nil { | |||
| 642 | - | return fmt.Errorf("failed to get child folders: %w", err) | |||
| 643 | } | 522 | } | ||
| 644 | - | ||||
| 645 | - | // Recursively delete child folders | |||
| 646 | - | for _, child := range children { | |||
| 647 | - | if err := uc.DeleteFolder(ctx, child.ID); err != nil { | |||
| 648 | - | log.Warn(). | |||
| 649 | - | Int64("child_id", int64(child.ID)). | |||
| 650 | - | Err(err). | |||
| 651 | - | Msg("failed to delete child folder") | |||
| 652 | - | } | |||
| 653 | - | } | |||
| 654 | - | ||||
| 655 | - | // Delete the folder itself | |||
| 656 | - | if err := uc.folderRepo.Delete(ctx, id); err != nil { | |||
| 657 | - | return fmt.Errorf("failed to delete folder: %w", err) | |||
| 658 | - | } | |||
| 659 | - | ||||
| 660 | - | log.Info().Int64("id", int64(id)).Msg("folder deleted") | |||
| 661 | - | return nil | |||
| 662 | - | } | |||
| 663 | - | ||||
| 664 | - | // GetAllFolders retrieves all folders. | |||
| 665 | - | func (uc *ManageFavoritesUseCase) GetAllFolders(ctx context.Context) ([]*entity.Folder, error) { | |||
| 666 | - | log := logging.FromContext(ctx) | |||
| 667 | - | log.Debug().Msg("getting all folders") | |||
| 668 | - | ||||
| 669 | - | return uc.folderRepo.GetAll(ctx) | |||
| 670 | - | } | |||
| 671 | - | ||||
| 672 | - | // AddTag creates a new tag. | |||
| 673 | - | func (uc *ManageFavoritesUseCase) AddTag(ctx context.Context, name, color string) (*entity.Tag, error) { | |||
| 674 | log := logging.FromContext(ctx) | 523 | log := logging.FromContext(ctx) | ||
| 675 | log.Debug().Str("name", name).Str("color", color).Msg("creating tag") | 524 | log.Debug().Str("name", name).Str("color", color).Msg("creating tag") | ||
| 676 | 525 | ||||
| @@ -759,34 +608,13 @@ func (uc *ManageFavoritesUseCase) GetTagsForFavorite(ctx context.Context, favID | |||||
| 759 | return uc.tagRepo.GetForFavorite(ctx, favID) | 608 | return uc.tagRepo.GetForFavorite(ctx, favID) | ||
| 760 | } | 609 | } | ||
| 761 | 610 | ||||
| 762 | - | // UpdateFolder updates a folder's name and icon. | |||
| 763 | - | func (uc *ManageFavoritesUseCase) UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error { | |||
| 764 | - | log := logging.FromContext(ctx) | |||
| 765 | - | log.Debug().Int64("id", int64(id)).Str("name", name).Msg("updating folder") | |||
| 766 | - | ||||
| 767 | - | folder, err := uc.folderRepo.FindByID(ctx, id) | |||
| 768 | - | if err != nil { | |||
| 769 | - | return fmt.Errorf("failed to find folder: %w", err) | |||
| 770 | - | } | |||
| 771 | - | if folder == nil { | |||
| 772 | - | return fmt.Errorf("folder %d not found", id) | |||
| 773 | - | } | |||
| 774 | - | ||||
| 775 | - | if name != "" { | |||
| 776 | - | folder.Name = name | |||
| 777 | - | } | |||
| 778 | - | folder.Icon = icon | |||
| 779 | - | ||||
| 780 | - | if err := uc.folderRepo.Save(ctx, folder); err != nil { | |||
| 781 | - | return fmt.Errorf("failed to update folder: %w", err) | |||
| 782 | - | } | |||
| 783 | - | ||||
| 784 | - | log.Info().Int64("id", int64(id)).Msg("folder updated") | |||
| 785 | - | return nil | |||
| 786 | - | } | |||
| 787 | - | ||||
| 788 | // UpdateTag updates a tag's name and color. | 611 | // UpdateTag updates a tag's name and color. | ||
| 789 | func (uc *ManageFavoritesUseCase) UpdateTag(ctx context.Context, id entity.TagID, name, color string) error { | 612 | func (uc *ManageFavoritesUseCase) UpdateTag(ctx context.Context, id entity.TagID, name, color string) error { | ||
| 613 | + | name = strings.TrimSpace(name) | |||
| 614 | + | color = strings.TrimSpace(color) | |||
| 615 | + | if name == "" { | |||
| 616 | + | return fmt.Errorf("tag name is required") | |||
| 617 | + | } | |||
| 790 | log := logging.FromContext(ctx) | 618 | log := logging.FromContext(ctx) | ||
| 791 | log.Debug().Int64("id", int64(id)).Str("name", name).Str("color", color).Msg("updating tag") | 619 | log.Debug().Int64("id", int64(id)).Str("name", name).Str("color", color).Msg("updating tag") | ||
| 792 | 620 | ||||
| @@ -798,9 +626,7 @@ func (uc *ManageFavoritesUseCase) UpdateTag(ctx context.Context, id entity.TagID | |||||
| 798 | return fmt.Errorf("tag %d not found", id) | 626 | return fmt.Errorf("tag %d not found", id) | ||
| 799 | } | 627 | } | ||
| 800 | 628 | ||||
| 801 | - | if name != "" { | 629 | + | tag.Name = name |
| 802 | - | tag.Name = name | |||
| 803 | - | } | |||
| 804 | if color != "" { | 630 | if color != "" { | ||
| 805 | tag.Color = color | 631 | tag.Color = color | ||
| 806 | } | 632 | } | ||
M internal/application/usecase/manage_favorites_cache_test.go +1 -2
| @@ -17,7 +17,6 @@ func TestManageFavoritesUseCase_GetAllDoesNotOverwriteFreshCache(t *testing.T) { | |||
| 17 | 17 | ctx := logging.WithContext(context.Background(), logger) | |
| 18 | 18 | ||
| 19 | 19 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 20 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 21 | 20 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 22 | 21 | ||
| 23 | 22 | fetchedFavorites := []*entity.Favorite{ | |
| @@ -27,7 +26,7 @@ func TestManageFavoritesUseCase_GetAllDoesNotOverwriteFreshCache(t *testing.T) { | |||
| 27 | 26 | {ID: 2, URL: "https://fresh.example"}, | |
| 28 | 27 | } | |
| 29 | 28 | ||
| 30 | - | uc := NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 29 | + | uc := NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 31 | 30 | favoriteRepo.EXPECT().GetAll(ctx).Run(func(_ context.Context) { | |
| 32 | 31 | uc.cacheMu.Lock() | |
| 33 | 32 | uc.favoritesCache = refreshedFavorites | |
| @@ -17,7 +17,6 @@ func TestManageFavoritesUseCase_GetAllDoesNotOverwriteFreshCache(t *testing.T) { | |||||
| 17 | ctx := logging.WithContext(context.Background(), logger) | 17 | ctx := logging.WithContext(context.Background(), logger) | ||
| 18 | 18 | ||||
| 19 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 19 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 20 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 21 | tagRepo := repomocks.NewMockTagRepository(t) | 20 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 22 | 21 | ||||
| 23 | fetchedFavorites := []*entity.Favorite{ | 22 | fetchedFavorites := []*entity.Favorite{ | ||
| @@ -27,7 +26,7 @@ func TestManageFavoritesUseCase_GetAllDoesNotOverwriteFreshCache(t *testing.T) { | |||||
| 27 | {ID: 2, URL: "https://fresh.example"}, | 26 | {ID: 2, URL: "https://fresh.example"}, | ||
| 28 | } | 27 | } | ||
| 29 | 28 | ||||
| 30 | - | uc := NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 29 | + | uc := NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 31 | favoriteRepo.EXPECT().GetAll(ctx).Run(func(_ context.Context) { | 30 | favoriteRepo.EXPECT().GetAll(ctx).Run(func(_ context.Context) { | ||
| 32 | uc.cacheMu.Lock() | 31 | uc.cacheMu.Lock() | ||
| 33 | uc.favoritesCache = refreshedFavorites | 32 | uc.favoritesCache = refreshedFavorites | ||
M internal/application/usecase/manage_favorites_test.go +47 -76
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package usecase_test | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "context" | |
| 4 | 5 | "errors" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| @@ -17,7 +18,6 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsURLSet(t *testing.T) { | |||
| 17 | 18 | ctx := testContext() | |
| 18 | 19 | ||
| 19 | 20 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 20 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 21 | 21 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 22 | 22 | ||
| 23 | 23 | favorites := []*entity.Favorite{ | |
| @@ -28,7 +28,7 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsURLSet(t *testing.T) { | |||
| 28 | 28 | ||
| 29 | 29 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | |
| 30 | 30 | ||
| 31 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 31 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 32 | 32 | ||
| 33 | 33 | urls, err := uc.GetAllURLs(ctx) | |
| 34 | 34 | require.NoError(t, err) | |
| @@ -47,12 +47,11 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsEmptySetWhenNoFavorites(t *tes | |||
| 47 | 47 | ctx := testContext() | |
| 48 | 48 | ||
| 49 | 49 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 50 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 51 | 50 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 52 | 51 | ||
| 53 | 52 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil) | |
| 54 | 53 | ||
| 55 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 54 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 56 | 55 | ||
| 57 | 56 | urls, err := uc.GetAllURLs(ctx) | |
| 58 | 57 | require.NoError(t, err) | |
| @@ -64,12 +63,11 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsErrorOnRepoFailure(t *testing. | |||
| 64 | 63 | ctx := testContext() | |
| 65 | 64 | ||
| 66 | 65 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 67 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 68 | 66 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 69 | 67 | ||
| 70 | 68 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(nil, errors.New("db connection failed")) | |
| 71 | 69 | ||
| 72 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 70 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 73 | 71 | ||
| 74 | 72 | urls, err := uc.GetAllURLs(ctx) | |
| 75 | 73 | require.Error(t, err) | |
| @@ -81,7 +79,6 @@ func TestManageFavoritesUseCase_Toggle_AddsWhenNotFavorite(t *testing.T) { | |||
| 81 | 79 | ctx := testContext() | |
| 82 | 80 | ||
| 83 | 81 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 84 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 85 | 82 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 86 | 83 | ||
| 87 | 84 | // URL is not a favorite | |
| @@ -90,7 +87,7 @@ func TestManageFavoritesUseCase_Toggle_AddsWhenNotFavorite(t *testing.T) { | |||
| 90 | 87 | return f.URL == "https://example.com" && f.Title == "Example" | |
| 91 | 88 | })).Return(nil) | |
| 92 | 89 | ||
| 93 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 90 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 94 | 91 | ||
| 95 | 92 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | |
| 96 | 93 | require.NoError(t, err) | |
| @@ -106,7 +103,6 @@ func TestManageFavoritesUseCase_Toggle_RemovesWhenAlreadyFavorite(t *testing.T) | |||
| 106 | 103 | ctx := testContext() | |
| 107 | 104 | ||
| 108 | 105 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 109 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 110 | 106 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 111 | 107 | ||
| 112 | 108 | existingFav := &entity.Favorite{ | |
| @@ -119,7 +115,7 @@ func TestManageFavoritesUseCase_Toggle_RemovesWhenAlreadyFavorite(t *testing.T) | |||
| 119 | 115 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | |
| 120 | 116 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(nil) | |
| 121 | 117 | ||
| 122 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 118 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 123 | 119 | ||
| 124 | 120 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | |
| 125 | 121 | require.NoError(t, err) | |
| @@ -135,10 +131,9 @@ func TestManageFavoritesUseCase_Toggle_EmptyURLReturnsError(t *testing.T) { | |||
| 135 | 131 | ctx := testContext() | |
| 136 | 132 | ||
| 137 | 133 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 138 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 139 | 134 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 140 | 135 | ||
| 141 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 136 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 142 | 137 | ||
| 143 | 138 | result, err := uc.Toggle(ctx, "", "Title") | |
| 144 | 139 | require.Error(t, err) | |
| @@ -150,7 +145,6 @@ func TestManageFavoritesUseCase_ToggleNormalizesURLBeforeLookup(t *testing.T) { | |||
| 150 | 145 | ctx := testContext() | |
| 151 | 146 | ||
| 152 | 147 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 153 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 154 | 148 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 155 | 149 | ||
| 156 | 150 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | |
| @@ -159,7 +153,7 @@ func TestManageFavoritesUseCase_ToggleNormalizesURLBeforeLookup(t *testing.T) { | |||
| 159 | 153 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | |
| 160 | 154 | })).Return(nil).Once() | |
| 161 | 155 | ||
| 162 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 156 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 163 | 157 | ||
| 164 | 158 | result, err := uc.Toggle(ctx, " example.com ", " Example ") | |
| 165 | 159 | require.NoError(t, err) | |
| @@ -173,12 +167,11 @@ func TestManageFavoritesUseCase_Toggle_FindByURLErrorReturnsError(t *testing.T) | |||
| 173 | 167 | ctx := testContext() | |
| 174 | 168 | ||
| 175 | 169 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 176 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 177 | 170 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 178 | 171 | ||
| 179 | 172 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, errors.New("db error")) | |
| 180 | 173 | ||
| 181 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 174 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 182 | 175 | ||
| 183 | 176 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | |
| 184 | 177 | require.Error(t, err) | |
| @@ -190,13 +183,12 @@ func TestManageFavoritesUseCase_Toggle_SaveErrorReturnsError(t *testing.T) { | |||
| 190 | 183 | ctx := testContext() | |
| 191 | 184 | ||
| 192 | 185 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 193 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 194 | 186 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 195 | 187 | ||
| 196 | 188 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil) | |
| 197 | 189 | favoriteRepo.EXPECT().Save(mock.Anything, mock.Anything).Return(errors.New("db error")) | |
| 198 | 190 | ||
| 199 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 191 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 200 | 192 | ||
| 201 | 193 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | |
| 202 | 194 | require.Error(t, err) | |
| @@ -208,14 +200,13 @@ func TestManageFavoritesUseCase_Toggle_DeleteErrorReturnsError(t *testing.T) { | |||
| 208 | 200 | ctx := testContext() | |
| 209 | 201 | ||
| 210 | 202 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 211 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 212 | 203 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 213 | 204 | ||
| 214 | 205 | existingFav := &entity.Favorite{ID: 42, URL: "https://example.com", Title: "Example"} | |
| 215 | 206 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | |
| 216 | 207 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(errors.New("db error")) | |
| 217 | 208 | ||
| 218 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 209 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 219 | 210 | ||
| 220 | 211 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | |
| 221 | 212 | require.Error(t, err) | |
| @@ -227,7 +218,6 @@ func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *te | |||
| 227 | 218 | ctx := testContext() | |
| 228 | 219 | ||
| 229 | 220 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 230 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 231 | 221 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 232 | 222 | ||
| 233 | 223 | favorites := []*entity.Favorite{ | |
| @@ -237,7 +227,7 @@ func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *te | |||
| 237 | 227 | } | |
| 238 | 228 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | |
| 239 | 229 | ||
| 240 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 230 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 241 | 231 | ||
| 242 | 232 | results, err := uc.FilterForOmnibox(ctx, "git") | |
| 243 | 233 | require.NoError(t, err) | |
| @@ -251,7 +241,6 @@ func TestManageFavoritesUseCase_FilterForOmnibox_EmptyQueryKeepsRepositoryOrder( | |||
| 251 | 241 | ctx := testContext() | |
| 252 | 242 | ||
| 253 | 243 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 254 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 255 | 244 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 256 | 245 | ||
| 257 | 246 | favorites := []*entity.Favorite{ | |
| @@ -260,7 +249,7 @@ func TestManageFavoritesUseCase_FilterForOmnibox_EmptyQueryKeepsRepositoryOrder( | |||
| 260 | 249 | } | |
| 261 | 250 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | |
| 262 | 251 | ||
| 263 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 252 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 264 | 253 | ||
| 265 | 254 | results, err := uc.FilterForOmnibox(ctx, "") | |
| 266 | 255 | require.NoError(t, err) | |
| @@ -273,7 +262,6 @@ func TestManageFavoritesUseCase_GetAllURLs_UsesCache(t *testing.T) { | |||
| 273 | 262 | ctx := testContext() | |
| 274 | 263 | ||
| 275 | 264 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 276 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 277 | 265 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 278 | 266 | ||
| 279 | 267 | favorites := []*entity.Favorite{ | |
| @@ -281,7 +269,7 @@ func TestManageFavoritesUseCase_GetAllURLs_UsesCache(t *testing.T) { | |||
| 281 | 269 | } | |
| 282 | 270 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once() | |
| 283 | 271 | ||
| 284 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 272 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 285 | 273 | ||
| 286 | 274 | urls, err := uc.GetAllURLs(ctx) | |
| 287 | 275 | require.NoError(t, err) | |
| @@ -296,7 +284,6 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||
| 296 | 284 | ctx := testContext() | |
| 297 | 285 | ||
| 298 | 286 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 299 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 300 | 287 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 301 | 288 | ||
| 302 | 289 | initialFavorites := []*entity.Favorite{ | |
| @@ -313,7 +300,7 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||
| 313 | 300 | } | |
| 314 | 301 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once() | |
| 315 | 302 | ||
| 316 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 303 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 317 | 304 | ||
| 318 | 305 | urls, err := uc.GetAllURLs(ctx) | |
| 319 | 306 | require.NoError(t, err) | |
| @@ -327,51 +314,10 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||
| 327 | 314 | require.Contains(t, urls, "https://new.example") | |
| 328 | 315 | } | |
| 329 | 316 | ||
| 330 | - | func TestManageFavoritesUseCase_DeleteFolder_InvalidatesFavoritesCache(t *testing.T) { | |
| 331 | - | ctx := testContext() | |
| 332 | - | ||
| 333 | - | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 334 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 335 | - | tagRepo := repomocks.NewMockTagRepository(t) | |
| 336 | - | ||
| 337 | - | folderID := entity.FolderID(9) | |
| 338 | - | initialFolderID := folderID | |
| 339 | - | cachedFavorites := []*entity.Favorite{ | |
| 340 | - | {ID: 1, URL: "https://example.com", FolderID: &initialFolderID}, | |
| 341 | - | } | |
| 342 | - | updatedFavorites := []*entity.Favorite{ | |
| 343 | - | {ID: 1, URL: "https://example.com", FolderID: nil}, | |
| 344 | - | } | |
| 345 | - | ||
| 346 | - | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(cachedFavorites, nil).Once() | |
| 347 | - | folderRepo.EXPECT().FindByID(mock.Anything, folderID).Return(&entity.Folder{ID: folderID}, nil).Once() | |
| 348 | - | favoriteRepo.EXPECT().GetByFolder(mock.Anything, &folderID).Return(cachedFavorites, nil).Once() | |
| 349 | - | favoriteRepo.EXPECT().SetFolder(mock.Anything, entity.FavoriteID(1), (*entity.FolderID)(nil)).Return(nil).Once() | |
| 350 | - | folderRepo.EXPECT().GetChildren(mock.Anything, &folderID).Return([]*entity.Folder{}, nil).Once() | |
| 351 | - | folderRepo.EXPECT().Delete(mock.Anything, folderID).Return(nil).Once() | |
| 352 | - | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once() | |
| 353 | - | ||
| 354 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 355 | - | ||
| 356 | - | favoritesBefore, err := uc.GetAll(ctx) | |
| 357 | - | require.NoError(t, err) | |
| 358 | - | require.Len(t, favoritesBefore, 1) | |
| 359 | - | require.NotNil(t, favoritesBefore[0].FolderID) | |
| 360 | - | ||
| 361 | - | err = uc.DeleteFolder(ctx, folderID) | |
| 362 | - | require.NoError(t, err) | |
| 363 | - | ||
| 364 | - | favoritesAfter, err := uc.GetAll(ctx) | |
| 365 | - | require.NoError(t, err) | |
| 366 | - | require.Len(t, favoritesAfter, 1) | |
| 367 | - | assert.Nil(t, favoritesAfter[0].FolderID) | |
| 368 | - | } | |
| 369 | - | ||
| 370 | 317 | func TestManageFavoritesUseCase_AddFavoriteNormalizesURL(t *testing.T) { | |
| 371 | 318 | ctx := testContext() | |
| 372 | 319 | ||
| 373 | 320 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 374 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 375 | 321 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 376 | 322 | ||
| 377 | 323 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | |
| @@ -380,7 +326,7 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesURL(t *testing.T) { | |||
| 380 | 326 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | |
| 381 | 327 | })).Return(nil).Once() | |
| 382 | 328 | ||
| 383 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 329 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 384 | 330 | ||
| 385 | 331 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "example.com", Title: " Example "}) | |
| 386 | 332 | require.NoError(t, err) | |
| @@ -393,15 +339,42 @@ func TestManageFavoritesUseCase_AddFavoriteRejectsInvalidTagIDs(t *testing.T) { | |||
| 393 | 339 | ctx := testContext() | |
| 394 | 340 | ||
| 395 | 341 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 396 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 397 | 342 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 398 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 343 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 399 | 344 | ||
| 400 | 345 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "https://example.com", Tags: []entity.TagID{1, 0}}) | |
| 401 | 346 | require.Error(t, err) | |
| 402 | 347 | assert.Contains(t, err.Error(), "favorite tag id must be positive") | |
| 403 | 348 | } | |
| 404 | 349 | ||
| 350 | + | func TestManageFavoritesUseCase_AddFavoriteAssignsTagsWithoutFolder(t *testing.T) { | |
| 351 | + | ctx := testContext() | |
| 352 | + | ||
| 353 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 354 | + | tagRepo := repomocks.NewMockTagRepository(t) | |
| 355 | + | ||
| 356 | + | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | |
| 357 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |
| 358 | + | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" && fav.FaviconURL == "https://example.com/favicon.ico" | |
| 359 | + | })).Run(func(_ context.Context, fav *entity.Favorite) { | |
| 360 | + | fav.ID = entity.FavoriteID(42) | |
| 361 | + | }).Return(nil).Once() | |
| 362 | + | tagRepo.EXPECT().AssignToFavorite(mock.Anything, entity.TagID(7), entity.FavoriteID(42)).Return(nil).Once() | |
| 363 | + | tagRepo.EXPECT().AssignToFavorite(mock.Anything, entity.TagID(8), entity.FavoriteID(42)).Return(nil).Once() | |
| 364 | + | ||
| 365 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 366 | + | ||
| 367 | + | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{ | |
| 368 | + | URL: "https://example.com", | |
| 369 | + | Title: "Example", | |
| 370 | + | FaviconURL: " https://example.com/favicon.ico ", | |
| 371 | + | Tags: []entity.TagID{7, 8}, | |
| 372 | + | }) | |
| 373 | + | require.NoError(t, err) | |
| 374 | + | require.NotNil(t, fav) | |
| 375 | + | assert.Equal(t, entity.FavoriteID(42), fav.ID) | |
| 376 | + | } | |
| 377 | + | ||
| 405 | 378 | func TestManageFavoritesUseCase_AddFavoriteRejectsUnsafeOrMalformedURL(t *testing.T) { | |
| 406 | 379 | ctx := testContext() | |
| 407 | 380 | ||
| @@ -416,9 +389,8 @@ func TestManageFavoritesUseCase_AddFavoriteRejectsUnsafeOrMalformedURL(t *testin | |||
| 416 | 389 | for _, raw := range tests { | |
| 417 | 390 | t.Run(raw, func(t *testing.T) { | |
| 418 | 391 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 419 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 420 | 392 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 421 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 393 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 422 | 394 | ||
| 423 | 395 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: raw}) | |
| 424 | 396 | require.Error(t, err) | |
| @@ -430,7 +402,6 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesOpaqueDumbRoute(t *testing. | |||
| 430 | 402 | ctx := testContext() | |
| 431 | 403 | ||
| 432 | 404 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 433 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |
| 434 | 405 | tagRepo := repomocks.NewMockTagRepository(t) | |
| 435 | 406 | ||
| 436 | 407 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "dumb://history").Return(nil, nil).Once() | |
| @@ -439,7 +410,7 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesOpaqueDumbRoute(t *testing. | |||
| 439 | 410 | return fav != nil && fav.URL == "dumb://history" && fav.Title == "History" | |
| 440 | 411 | })).Return(nil).Once() | |
| 441 | 412 | ||
| 442 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |
| 413 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 443 | 414 | ||
| 444 | 415 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "dumb:history", Title: "History"}) | |
| 445 | 416 | require.NoError(t, err) | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package usecase_test | 1 | package usecase_test | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "context" | |||
| 4 | "errors" | 5 | "errors" | ||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| @@ -17,7 +18,6 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsURLSet(t *testing.T) { | |||||
| 17 | ctx := testContext() | 18 | ctx := testContext() | ||
| 18 | 19 | ||||
| 19 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 20 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 20 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 21 | tagRepo := repomocks.NewMockTagRepository(t) | 21 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 22 | 22 | ||||
| 23 | favorites := []*entity.Favorite{ | 23 | favorites := []*entity.Favorite{ | ||
| @@ -28,7 +28,7 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsURLSet(t *testing.T) { | |||||
| 28 | 28 | ||||
| 29 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | 29 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | ||
| 30 | 30 | ||||
| 31 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 31 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 32 | 32 | ||||
| 33 | urls, err := uc.GetAllURLs(ctx) | 33 | urls, err := uc.GetAllURLs(ctx) | ||
| 34 | require.NoError(t, err) | 34 | require.NoError(t, err) | ||
| @@ -47,12 +47,11 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsEmptySetWhenNoFavorites(t *tes | |||||
| 47 | ctx := testContext() | 47 | ctx := testContext() | ||
| 48 | 48 | ||||
| 49 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 49 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 50 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 51 | tagRepo := repomocks.NewMockTagRepository(t) | 50 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 52 | 51 | ||||
| 53 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil) | 52 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil) | ||
| 54 | 53 | ||||
| 55 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 54 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 56 | 55 | ||||
| 57 | urls, err := uc.GetAllURLs(ctx) | 56 | urls, err := uc.GetAllURLs(ctx) | ||
| 58 | require.NoError(t, err) | 57 | require.NoError(t, err) | ||
| @@ -64,12 +63,11 @@ func TestManageFavoritesUseCase_GetAllURLs_ReturnsErrorOnRepoFailure(t *testing. | |||||
| 64 | ctx := testContext() | 63 | ctx := testContext() | ||
| 65 | 64 | ||||
| 66 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 65 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 67 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 68 | tagRepo := repomocks.NewMockTagRepository(t) | 66 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 69 | 67 | ||||
| 70 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(nil, errors.New("db connection failed")) | 68 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(nil, errors.New("db connection failed")) | ||
| 71 | 69 | ||||
| 72 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 70 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 73 | 71 | ||||
| 74 | urls, err := uc.GetAllURLs(ctx) | 72 | urls, err := uc.GetAllURLs(ctx) | ||
| 75 | require.Error(t, err) | 73 | require.Error(t, err) | ||
| @@ -81,7 +79,6 @@ func TestManageFavoritesUseCase_Toggle_AddsWhenNotFavorite(t *testing.T) { | |||||
| 81 | ctx := testContext() | 79 | ctx := testContext() | ||
| 82 | 80 | ||||
| 83 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 81 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 84 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 85 | tagRepo := repomocks.NewMockTagRepository(t) | 82 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 86 | 83 | ||||
| 87 | // URL is not a favorite | 84 | // URL is not a favorite | ||
| @@ -90,7 +87,7 @@ func TestManageFavoritesUseCase_Toggle_AddsWhenNotFavorite(t *testing.T) { | |||||
| 90 | return f.URL == "https://example.com" && f.Title == "Example" | 87 | return f.URL == "https://example.com" && f.Title == "Example" | ||
| 91 | })).Return(nil) | 88 | })).Return(nil) | ||
| 92 | 89 | ||||
| 93 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 90 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 94 | 91 | ||||
| 95 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | 92 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | ||
| 96 | require.NoError(t, err) | 93 | require.NoError(t, err) | ||
| @@ -106,7 +103,6 @@ func TestManageFavoritesUseCase_Toggle_RemovesWhenAlreadyFavorite(t *testing.T) | |||||
| 106 | ctx := testContext() | 103 | ctx := testContext() | ||
| 107 | 104 | ||||
| 108 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 105 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 109 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 110 | tagRepo := repomocks.NewMockTagRepository(t) | 106 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 111 | 107 | ||||
| 112 | existingFav := &entity.Favorite{ | 108 | existingFav := &entity.Favorite{ | ||
| @@ -119,7 +115,7 @@ func TestManageFavoritesUseCase_Toggle_RemovesWhenAlreadyFavorite(t *testing.T) | |||||
| 119 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | 115 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | ||
| 120 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(nil) | 116 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(nil) | ||
| 121 | 117 | ||||
| 122 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 118 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 123 | 119 | ||||
| 124 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | 120 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | ||
| 125 | require.NoError(t, err) | 121 | require.NoError(t, err) | ||
| @@ -135,10 +131,9 @@ func TestManageFavoritesUseCase_Toggle_EmptyURLReturnsError(t *testing.T) { | |||||
| 135 | ctx := testContext() | 131 | ctx := testContext() | ||
| 136 | 132 | ||||
| 137 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 133 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 138 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 139 | tagRepo := repomocks.NewMockTagRepository(t) | 134 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 140 | 135 | ||||
| 141 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 136 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 142 | 137 | ||||
| 143 | result, err := uc.Toggle(ctx, "", "Title") | 138 | result, err := uc.Toggle(ctx, "", "Title") | ||
| 144 | require.Error(t, err) | 139 | require.Error(t, err) | ||
| @@ -150,7 +145,6 @@ func TestManageFavoritesUseCase_ToggleNormalizesURLBeforeLookup(t *testing.T) { | |||||
| 150 | ctx := testContext() | 145 | ctx := testContext() | ||
| 151 | 146 | ||||
| 152 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 147 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 153 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 154 | tagRepo := repomocks.NewMockTagRepository(t) | 148 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 155 | 149 | ||||
| 156 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | 150 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | ||
| @@ -159,7 +153,7 @@ func TestManageFavoritesUseCase_ToggleNormalizesURLBeforeLookup(t *testing.T) { | |||||
| 159 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | 153 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | ||
| 160 | })).Return(nil).Once() | 154 | })).Return(nil).Once() | ||
| 161 | 155 | ||||
| 162 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 156 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 163 | 157 | ||||
| 164 | result, err := uc.Toggle(ctx, " example.com ", " Example ") | 158 | result, err := uc.Toggle(ctx, " example.com ", " Example ") | ||
| 165 | require.NoError(t, err) | 159 | require.NoError(t, err) | ||
| @@ -173,12 +167,11 @@ func TestManageFavoritesUseCase_Toggle_FindByURLErrorReturnsError(t *testing.T) | |||||
| 173 | ctx := testContext() | 167 | ctx := testContext() | ||
| 174 | 168 | ||||
| 175 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 169 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 176 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 177 | tagRepo := repomocks.NewMockTagRepository(t) | 170 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 178 | 171 | ||||
| 179 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, errors.New("db error")) | 172 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, errors.New("db error")) | ||
| 180 | 173 | ||||
| 181 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 174 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 182 | 175 | ||||
| 183 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | 176 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | ||
| 184 | require.Error(t, err) | 177 | require.Error(t, err) | ||
| @@ -190,13 +183,12 @@ func TestManageFavoritesUseCase_Toggle_SaveErrorReturnsError(t *testing.T) { | |||||
| 190 | ctx := testContext() | 183 | ctx := testContext() | ||
| 191 | 184 | ||||
| 192 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 185 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 193 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 194 | tagRepo := repomocks.NewMockTagRepository(t) | 186 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 195 | 187 | ||||
| 196 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil) | 188 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil) | ||
| 197 | favoriteRepo.EXPECT().Save(mock.Anything, mock.Anything).Return(errors.New("db error")) | 189 | favoriteRepo.EXPECT().Save(mock.Anything, mock.Anything).Return(errors.New("db error")) | ||
| 198 | 190 | ||||
| 199 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 191 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 200 | 192 | ||||
| 201 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | 193 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | ||
| 202 | require.Error(t, err) | 194 | require.Error(t, err) | ||
| @@ -208,14 +200,13 @@ func TestManageFavoritesUseCase_Toggle_DeleteErrorReturnsError(t *testing.T) { | |||||
| 208 | ctx := testContext() | 200 | ctx := testContext() | ||
| 209 | 201 | ||||
| 210 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 202 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 211 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 212 | tagRepo := repomocks.NewMockTagRepository(t) | 203 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 213 | 204 | ||||
| 214 | existingFav := &entity.Favorite{ID: 42, URL: "https://example.com", Title: "Example"} | 205 | existingFav := &entity.Favorite{ID: 42, URL: "https://example.com", Title: "Example"} | ||
| 215 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | 206 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(existingFav, nil) | ||
| 216 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(errors.New("db error")) | 207 | favoriteRepo.EXPECT().Delete(mock.Anything, entity.FavoriteID(42)).Return(errors.New("db error")) | ||
| 217 | 208 | ||||
| 218 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 209 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 219 | 210 | ||||
| 220 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | 211 | result, err := uc.Toggle(ctx, "https://example.com", "Example") | ||
| 221 | require.Error(t, err) | 212 | require.Error(t, err) | ||
| @@ -227,7 +218,6 @@ func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *te | |||||
| 227 | ctx := testContext() | 218 | ctx := testContext() | ||
| 228 | 219 | ||||
| 229 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 220 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 230 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 231 | tagRepo := repomocks.NewMockTagRepository(t) | 221 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 232 | 222 | ||||
| 233 | favorites := []*entity.Favorite{ | 223 | favorites := []*entity.Favorite{ | ||
| @@ -237,7 +227,7 @@ func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *te | |||||
| 237 | } | 227 | } | ||
| 238 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | 228 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | ||
| 239 | 229 | ||||
| 240 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 230 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 241 | 231 | ||||
| 242 | results, err := uc.FilterForOmnibox(ctx, "git") | 232 | results, err := uc.FilterForOmnibox(ctx, "git") | ||
| 243 | require.NoError(t, err) | 233 | require.NoError(t, err) | ||
| @@ -251,7 +241,6 @@ func TestManageFavoritesUseCase_FilterForOmnibox_EmptyQueryKeepsRepositoryOrder( | |||||
| 251 | ctx := testContext() | 241 | ctx := testContext() | ||
| 252 | 242 | ||||
| 253 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 243 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 254 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 255 | tagRepo := repomocks.NewMockTagRepository(t) | 244 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 256 | 245 | ||||
| 257 | favorites := []*entity.Favorite{ | 246 | favorites := []*entity.Favorite{ | ||
| @@ -260,7 +249,7 @@ func TestManageFavoritesUseCase_FilterForOmnibox_EmptyQueryKeepsRepositoryOrder( | |||||
| 260 | } | 249 | } | ||
| 261 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | 250 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil) | ||
| 262 | 251 | ||||
| 263 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 252 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 264 | 253 | ||||
| 265 | results, err := uc.FilterForOmnibox(ctx, "") | 254 | results, err := uc.FilterForOmnibox(ctx, "") | ||
| 266 | require.NoError(t, err) | 255 | require.NoError(t, err) | ||
| @@ -273,7 +262,6 @@ func TestManageFavoritesUseCase_GetAllURLs_UsesCache(t *testing.T) { | |||||
| 273 | ctx := testContext() | 262 | ctx := testContext() | ||
| 274 | 263 | ||||
| 275 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 264 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 276 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 277 | tagRepo := repomocks.NewMockTagRepository(t) | 265 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 278 | 266 | ||||
| 279 | favorites := []*entity.Favorite{ | 267 | favorites := []*entity.Favorite{ | ||
| @@ -281,7 +269,7 @@ func TestManageFavoritesUseCase_GetAllURLs_UsesCache(t *testing.T) { | |||||
| 281 | } | 269 | } | ||
| 282 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once() | 270 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once() | ||
| 283 | 271 | ||||
| 284 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 272 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 285 | 273 | ||||
| 286 | urls, err := uc.GetAllURLs(ctx) | 274 | urls, err := uc.GetAllURLs(ctx) | ||
| 287 | require.NoError(t, err) | 275 | require.NoError(t, err) | ||
| @@ -296,7 +284,6 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||||
| 296 | ctx := testContext() | 284 | ctx := testContext() | ||
| 297 | 285 | ||||
| 298 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 286 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 299 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 300 | tagRepo := repomocks.NewMockTagRepository(t) | 287 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 301 | 288 | ||||
| 302 | initialFavorites := []*entity.Favorite{ | 289 | initialFavorites := []*entity.Favorite{ | ||
| @@ -313,7 +300,7 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||||
| 313 | } | 300 | } | ||
| 314 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once() | 301 | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once() | ||
| 315 | 302 | ||||
| 316 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 303 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 317 | 304 | ||||
| 318 | urls, err := uc.GetAllURLs(ctx) | 305 | urls, err := uc.GetAllURLs(ctx) | ||
| 319 | require.NoError(t, err) | 306 | require.NoError(t, err) | ||
| @@ -327,51 +314,10 @@ func TestManageFavoritesUseCase_Toggle_InvalidatesCache(t *testing.T) { | |||||
| 327 | require.Contains(t, urls, "https://new.example") | 314 | require.Contains(t, urls, "https://new.example") | ||
| 328 | } | 315 | } | ||
| 329 | 316 | ||||
| 330 | - | func TestManageFavoritesUseCase_DeleteFolder_InvalidatesFavoritesCache(t *testing.T) { | |||
| 331 | - | ctx := testContext() | |||
| 332 | - | ||||
| 333 | - | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |||
| 334 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 335 | - | tagRepo := repomocks.NewMockTagRepository(t) | |||
| 336 | - | ||||
| 337 | - | folderID := entity.FolderID(9) | |||
| 338 | - | initialFolderID := folderID | |||
| 339 | - | cachedFavorites := []*entity.Favorite{ | |||
| 340 | - | {ID: 1, URL: "https://example.com", FolderID: &initialFolderID}, | |||
| 341 | - | } | |||
| 342 | - | updatedFavorites := []*entity.Favorite{ | |||
| 343 | - | {ID: 1, URL: "https://example.com", FolderID: nil}, | |||
| 344 | - | } | |||
| 345 | - | ||||
| 346 | - | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(cachedFavorites, nil).Once() | |||
| 347 | - | folderRepo.EXPECT().FindByID(mock.Anything, folderID).Return(&entity.Folder{ID: folderID}, nil).Once() | |||
| 348 | - | favoriteRepo.EXPECT().GetByFolder(mock.Anything, &folderID).Return(cachedFavorites, nil).Once() | |||
| 349 | - | favoriteRepo.EXPECT().SetFolder(mock.Anything, entity.FavoriteID(1), (*entity.FolderID)(nil)).Return(nil).Once() | |||
| 350 | - | folderRepo.EXPECT().GetChildren(mock.Anything, &folderID).Return([]*entity.Folder{}, nil).Once() | |||
| 351 | - | folderRepo.EXPECT().Delete(mock.Anything, folderID).Return(nil).Once() | |||
| 352 | - | favoriteRepo.EXPECT().GetAll(mock.Anything).Return(updatedFavorites, nil).Once() | |||
| 353 | - | ||||
| 354 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | |||
| 355 | - | ||||
| 356 | - | favoritesBefore, err := uc.GetAll(ctx) | |||
| 357 | - | require.NoError(t, err) | |||
| 358 | - | require.Len(t, favoritesBefore, 1) | |||
| 359 | - | require.NotNil(t, favoritesBefore[0].FolderID) | |||
| 360 | - | ||||
| 361 | - | err = uc.DeleteFolder(ctx, folderID) | |||
| 362 | - | require.NoError(t, err) | |||
| 363 | - | ||||
| 364 | - | favoritesAfter, err := uc.GetAll(ctx) | |||
| 365 | - | require.NoError(t, err) | |||
| 366 | - | require.Len(t, favoritesAfter, 1) | |||
| 367 | - | assert.Nil(t, favoritesAfter[0].FolderID) | |||
| 368 | - | } | |||
| 369 | - | ||||
| 370 | func TestManageFavoritesUseCase_AddFavoriteNormalizesURL(t *testing.T) { | 317 | func TestManageFavoritesUseCase_AddFavoriteNormalizesURL(t *testing.T) { | ||
| 371 | ctx := testContext() | 318 | ctx := testContext() | ||
| 372 | 319 | ||||
| 373 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 320 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 374 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 375 | tagRepo := repomocks.NewMockTagRepository(t) | 321 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 376 | 322 | ||||
| 377 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | 323 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | ||
| @@ -380,7 +326,7 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesURL(t *testing.T) { | |||||
| 380 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | 326 | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" | ||
| 381 | })).Return(nil).Once() | 327 | })).Return(nil).Once() | ||
| 382 | 328 | ||||
| 383 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 329 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 384 | 330 | ||||
| 385 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "example.com", Title: " Example "}) | 331 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "example.com", Title: " Example "}) | ||
| 386 | require.NoError(t, err) | 332 | require.NoError(t, err) | ||
| @@ -393,15 +339,42 @@ func TestManageFavoritesUseCase_AddFavoriteRejectsInvalidTagIDs(t *testing.T) { | |||||
| 393 | ctx := testContext() | 339 | ctx := testContext() | ||
| 394 | 340 | ||||
| 395 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 341 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 396 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 397 | tagRepo := repomocks.NewMockTagRepository(t) | 342 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 398 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 343 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 399 | 344 | ||||
| 400 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "https://example.com", Tags: []entity.TagID{1, 0}}) | 345 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "https://example.com", Tags: []entity.TagID{1, 0}}) | ||
| 401 | require.Error(t, err) | 346 | require.Error(t, err) | ||
| 402 | assert.Contains(t, err.Error(), "favorite tag id must be positive") | 347 | assert.Contains(t, err.Error(), "favorite tag id must be positive") | ||
| 403 | } | 348 | } | ||
| 404 | 349 | ||||
| 350 | + | func TestManageFavoritesUseCase_AddFavoriteAssignsTagsWithoutFolder(t *testing.T) { | |||
| 351 | + | ctx := testContext() | |||
| 352 | + | ||||
| 353 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |||
| 354 | + | tagRepo := repomocks.NewMockTagRepository(t) | |||
| 355 | + | ||||
| 356 | + | favoriteRepo.EXPECT().FindByURL(mock.Anything, "https://example.com").Return(nil, nil).Once() | |||
| 357 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |||
| 358 | + | return fav != nil && fav.URL == "https://example.com" && fav.Title == "Example" && fav.FaviconURL == "https://example.com/favicon.ico" | |||
| 359 | + | })).Run(func(_ context.Context, fav *entity.Favorite) { | |||
| 360 | + | fav.ID = entity.FavoriteID(42) | |||
| 361 | + | }).Return(nil).Once() | |||
| 362 | + | tagRepo.EXPECT().AssignToFavorite(mock.Anything, entity.TagID(7), entity.FavoriteID(42)).Return(nil).Once() | |||
| 363 | + | tagRepo.EXPECT().AssignToFavorite(mock.Anything, entity.TagID(8), entity.FavoriteID(42)).Return(nil).Once() | |||
| 364 | + | ||||
| 365 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |||
| 366 | + | ||||
| 367 | + | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{ | |||
| 368 | + | URL: "https://example.com", | |||
| 369 | + | Title: "Example", | |||
| 370 | + | FaviconURL: " https://example.com/favicon.ico ", | |||
| 371 | + | Tags: []entity.TagID{7, 8}, | |||
| 372 | + | }) | |||
| 373 | + | require.NoError(t, err) | |||
| 374 | + | require.NotNil(t, fav) | |||
| 375 | + | assert.Equal(t, entity.FavoriteID(42), fav.ID) | |||
| 376 | + | } | |||
| 377 | + | ||||
| 405 | func TestManageFavoritesUseCase_AddFavoriteRejectsUnsafeOrMalformedURL(t *testing.T) { | 378 | func TestManageFavoritesUseCase_AddFavoriteRejectsUnsafeOrMalformedURL(t *testing.T) { | ||
| 406 | ctx := testContext() | 379 | ctx := testContext() | ||
| 407 | 380 | ||||
| @@ -416,9 +389,8 @@ func TestManageFavoritesUseCase_AddFavoriteRejectsUnsafeOrMalformedURL(t *testin | |||||
| 416 | for _, raw := range tests { | 389 | for _, raw := range tests { | ||
| 417 | t.Run(raw, func(t *testing.T) { | 390 | t.Run(raw, func(t *testing.T) { | ||
| 418 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 391 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 419 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 420 | tagRepo := repomocks.NewMockTagRepository(t) | 392 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 421 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 393 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 422 | 394 | ||||
| 423 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: raw}) | 395 | _, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: raw}) | ||
| 424 | require.Error(t, err) | 396 | require.Error(t, err) | ||
| @@ -430,7 +402,6 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesOpaqueDumbRoute(t *testing. | |||||
| 430 | ctx := testContext() | 402 | ctx := testContext() | ||
| 431 | 403 | ||||
| 432 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | 404 | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | ||
| 433 | - | folderRepo := repomocks.NewMockFolderRepository(t) | |||
| 434 | tagRepo := repomocks.NewMockTagRepository(t) | 405 | tagRepo := repomocks.NewMockTagRepository(t) | ||
| 435 | 406 | ||||
| 436 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "dumb://history").Return(nil, nil).Once() | 407 | favoriteRepo.EXPECT().FindByURL(mock.Anything, "dumb://history").Return(nil, nil).Once() | ||
| @@ -439,7 +410,7 @@ func TestManageFavoritesUseCase_AddFavoriteNormalizesOpaqueDumbRoute(t *testing. | |||||
| 439 | return fav != nil && fav.URL == "dumb://history" && fav.Title == "History" | 410 | return fav != nil && fav.URL == "dumb://history" && fav.Title == "History" | ||
| 440 | })).Return(nil).Once() | 411 | })).Return(nil).Once() | ||
| 441 | 412 | ||||
| 442 | - | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo) | 413 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) |
| 443 | 414 | ||||
| 444 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "dumb:history", Title: "History"}) | 415 | fav, err := uc.AddFavorite(ctx, dto.FavoriteCreateInput{URL: "dumb:history", Title: "History"}) | ||
| 445 | require.NoError(t, err) | 416 | require.NoError(t, err) | ||