diff --git a/internal/entry/repository.go b/internal/entry/repository.go index 60f8766..a1415fa 100644 --- a/internal/entry/repository.go +++ b/internal/entry/repository.go @@ -17,9 +17,129 @@ type ( Update(ctx context.Context, path string, update UpdateFunc) (*domain.Entry, error) Delete(ctx context.Context, path string) (bool, error) } + + dummyRepository struct{} + + stubRepository struct { + outputs []domain.Entry + output *domain.Entry + err error + ok bool + } + + spyRepository struct { + subRepository Repository + Calls int + Creates int + Deletes int + Fetches int + Gets int + Updates int + } + + // NOTE(toby3d): fakeRepository is already provided by memory sub-package. + // NOTE(toby3d): mockRepository is complicated. Mocking too much is bad. ) var ( ErrExist error = errors.New("this entry already exist") ErrNotExist error = errors.New("this entry is not exist") ) + +// NewDummyMediaRepository creates an empty repository to satisfy contracts. +// It is used in tests where repository working is not important. +func NewDummyEntryRepository() Repository { + return &dummyRepository{} +} + +func (dummyRepository) Create(_ context.Context, _ string, _ domain.Entry) error { return nil } +func (dummyRepository) Delete(_ context.Context, _ string) (bool, error) { return false, nil } +func (dummyRepository) Get(_ context.Context, _ string) (*domain.Entry, error) { return nil, nil } + +func (dummyRepository) Fetch(_ context.Context, _ string) ([]domain.Entry, int, error) { + return make([]domain.Entry, 0), 0, nil +} + +func (dummyRepository) Update(_ context.Context, _ string, _ UpdateFunc) (*domain.Entry, error) { + return nil, nil +} + +// NewStubEntryRepository creates a repository that always returns input as a +// output. It is used in tests where some dependency on the repository is +// required. +func NewStubEntryRepository(outputs []domain.Entry, output *domain.Entry, err error, ok bool) Repository { + return &stubRepository{ + outputs: outputs, + output: output, + err: err, + ok: ok, + } +} + +func (repo *stubRepository) Create(_ context.Context, _ string, _ domain.Entry) error { + return repo.err +} + +func (repo *stubRepository) Delete(ctx context.Context, path string) (bool, error) { + return repo.ok, repo.err +} + +func (repo *stubRepository) Fetch(ctx context.Context, path string) ([]domain.Entry, int, error) { + return repo.outputs, len(repo.outputs), repo.err +} + +func (repo *stubRepository) Get(ctx context.Context, path string) (*domain.Entry, error) { + return repo.output, repo.err +} + +func (repo *stubRepository) Update(ctx context.Context, path string, update UpdateFunc) (*domain.Entry, error) { + return repo.output, repo.err +} + +// NewSpyEntryRepository creates a spy repository which count outside calls, +// based on provided subRepo. If subRepo is nil, then DummyRepository will be +// used. +func NewSpyEntryRepository(subRepo Repository) *spyRepository { + if subRepo == nil { + subRepo = NewDummyEntryRepository() + } + + return &spyRepository{ + subRepository: subRepo, + Creates: 0, + Updates: 0, + Gets: 0, + Fetches: 0, + Deletes: 0, + } +} + +func (repo *spyRepository) Create(ctx context.Context, path string, e domain.Entry) error { + repo.Creates++ + + return repo.subRepository.Create(ctx, path, e) +} + +func (repo *spyRepository) Delete(ctx context.Context, path string) (bool, error) { + repo.Deletes++ + + return repo.subRepository.Delete(ctx, path) +} + +func (repo *spyRepository) Fetch(ctx context.Context, path string) ([]domain.Entry, int, error) { + repo.Fetches++ + + return repo.subRepository.Fetch(ctx, path) +} + +func (repo *spyRepository) Get(ctx context.Context, path string) (*domain.Entry, error) { + repo.Gets++ + + return repo.subRepository.Get(ctx, path) +} + +func (repo *spyRepository) Update(ctx context.Context, path string, update UpdateFunc) (*domain.Entry, error) { + repo.Updates++ + + return repo.subRepository.Update(ctx, path, update) +} diff --git a/internal/entry/usecase.go b/internal/entry/usecase.go index 3d759e9..10772ca 100644 --- a/internal/entry/usecase.go +++ b/internal/entry/usecase.go @@ -8,6 +8,12 @@ import ( ) type ( + UpdateOptions struct { + Add *domain.Entry + Replace *domain.Entry + Delete *domain.Entry + } + UseCase interface { // Create creates a new entry. Returns map or rel links, like Permalink // or created post, shortcode and syndication. @@ -16,7 +22,7 @@ type ( // Update updates exist entry properties on provided u. // // TODO(toby3d): return Location header if entry updates their URL. - Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) + Update(ctx context.Context, u *url.URL, options UpdateOptions) (*domain.Entry, error) // Delete destroy entry on provided URL. Delete(ctx context.Context, u *url.URL) (bool, error) @@ -41,17 +47,17 @@ func NewDummyUseCase() *dummyUseCase { return &dummyUseCase{} } -func (dummyUseCase) Create(ctx context.Context, e domain.Entry) (map[string]*url.URL, error) { +func (dummyUseCase) Create(_ context.Context, _ domain.Entry) (map[string]*url.URL, error) { return nil, nil } -func (dummyUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) { +func (dummyUseCase) Update(_ context.Context, _ *url.URL, _ UpdateOptions) (*domain.Entry, error) { return nil, nil } -func (dummyUseCase) Delete(ctx context.Context, u *url.URL) (bool, error) { return false, nil } -func (dummyUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.Entry, error) { return nil, nil } -func (dummyUseCase) Source(ctx context.Context, u *url.URL) (*domain.Entry, error) { return nil, nil } +func (dummyUseCase) Delete(_ context.Context, _ *url.URL) (bool, error) { return false, nil } +func (dummyUseCase) Undelete(_ context.Context, _ *url.URL) (*domain.Entry, error) { return nil, nil } +func (dummyUseCase) Source(_ context.Context, _ *url.URL) (*domain.Entry, error) { return nil, nil } func NewStubUseCase(err error, e *domain.Entry, ok bool) *stubUseCase { return &stubUseCase{ @@ -61,22 +67,22 @@ func NewStubUseCase(err error, e *domain.Entry, ok bool) *stubUseCase { } } -func (ucase *stubUseCase) Create(ctx context.Context, e domain.Entry) (*domain.Entry, error) { +func (ucase *stubUseCase) Create(_ context.Context, _ domain.Entry) (*domain.Entry, error) { return ucase.entry, ucase.err } -func (ucase *stubUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) { +func (ucase *stubUseCase) Update(_ context.Context, _ *url.URL, _ UpdateOptions) (*domain.Entry, error) { return ucase.entry, ucase.err } -func (ucase *stubUseCase) Delete(ctx context.Context, u *url.URL) (bool, error) { +func (ucase *stubUseCase) Delete(_ context.Context, _ *url.URL) (bool, error) { return ucase.ok, ucase.err } -func (ucase *stubUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.Entry, error) { +func (ucase *stubUseCase) Undelete(_ context.Context, _ *url.URL) (*domain.Entry, error) { return ucase.entry, ucase.err } -func (ucase *stubUseCase) Source(ctx context.Context, u *url.URL) (*domain.Entry, error) { +func (ucase *stubUseCase) Source(_ context.Context, _ *url.URL) (*domain.Entry, error) { return ucase.entry, ucase.err } diff --git a/internal/entry/usecase/entry_ucase.go b/internal/entry/usecase/entry_ucase.go index 7f42df7..ec591e2 100644 --- a/internal/entry/usecase/entry_ucase.go +++ b/internal/entry/usecase/entry_ucase.go @@ -73,14 +73,18 @@ func (ucase *entryUseCase) Undelete(ctx context.Context, u *url.URL) (*domain.En } // Update implements entry.UseCase. -func (ucase *entryUseCase) Update(ctx context.Context, u *url.URL, e domain.Entry) (*domain.Entry, error) { +func (ucase *entryUseCase) Update(ctx context.Context, u *url.URL, opts entry.UpdateOptions) (*domain.Entry, error) { result, err := ucase.entries.Update(ctx, u.RequestURI(), func(_ context.Context, e *domain.Entry) ( *domain.Entry, error, ) { e.DeletedAt = time.Time{} e.UpdatedAt = time.Now().UTC() - return nil, nil + // TODO(toby3d): add + // TODO(toby3d): update + // TODO(toby3d): delete + + return e, nil }) if err != nil { return nil, fmt.Errorf("cannot update entry: %w", err) diff --git a/internal/media/repository.go b/internal/media/repository.go index 5f54fe5..5b15a90 100644 --- a/internal/media/repository.go +++ b/internal/media/repository.go @@ -107,26 +107,26 @@ func NewSpyMediaRepository(subRepo Repository) *spyRepository { } } -func (repo *spyRepository) Create(_ context.Context, _ string, _ domain.File) error { +func (repo *spyRepository) Create(ctx context.Context, path string, f domain.File) error { repo.Creates++ - return repo.subRepository.Create(context.TODO(), "", domain.File{}) + return repo.subRepository.Create(ctx, path, f) } -func (repo *spyRepository) Get(_ context.Context, _ string) (*domain.File, error) { +func (repo *spyRepository) Get(ctx context.Context, path string) (*domain.File, error) { repo.Gets++ - return repo.subRepository.Get(context.TODO(), "") + return repo.subRepository.Get(ctx, path) } -func (repo *spyRepository) Update(_ context.Context, _ string, _ UpdateFunc) error { +func (repo *spyRepository) Update(ctx context.Context, path string, update UpdateFunc) error { repo.Updates++ - return repo.subRepository.Update(context.TODO(), "", nil) + return repo.subRepository.Update(ctx, path, update) } -func (repo *spyRepository) Delete(_ context.Context, _ string) error { +func (repo *spyRepository) Delete(ctx context.Context, path string) error { repo.Deletes++ - return repo.subRepository.Delete(context.TODO(), "") + return repo.subRepository.Delete(ctx, path) }