Files
RenewCMS/test/site_domain_test.go
T

166 lines
6.2 KiB
Go

package test
import (
"RenewCMS/internal/domain/article"
"RenewCMS/internal/domain/site"
"RenewCMS/internal/infrastructure/useCases"
"errors"
"strings"
"testing"
)
func TestSiteDomainRules(t *testing.T) {
t.Run("NormalizeBaseURL accepts what the rule allows and strips the trailing slash", func(t *testing.T) {
accepted := map[string]string{
"https://shop.example.com": "https://shop.example.com",
"https://shop.example.com/": "https://shop.example.com",
"https://SHOP.example.com:8443": "https://shop.example.com:8443",
"http://localhost": "http://localhost",
"http://[::1]:8080/": "http://[::1]:8080",
"HTTPS://shop.example.com": "https://shop.example.com",
}
for sent, stored := range accepted {
if got, err := site.NormalizeBaseURL(sent); err != nil || got != stored {
t.Errorf("%q gave %q, %v; expected %q", sent, got, err, stored)
}
}
})
t.Run("NormalizeBaseURL refuses everything else", func(t *testing.T) {
for _, sent := range []string{"", "http://shop.example.com", "https://", "https://shop.example.com/x", "https://shop.example.com?x", "https://shop.example.com/#top", "https://u:p@shop.example.com", "ftp://shop.example.com", "shop.example.com", "localhost:8080", "https://shop example.com"} {
if got, err := site.NormalizeBaseURL(sent); !errors.Is(err, site.ErrInvalidSiteURL) {
t.Errorf("%q gave %q, %v; expected ErrInvalidSiteURL", sent, got, err)
}
}
})
t.Run("SeedBaseURL drops the path and upgrades non-loopback http", func(t *testing.T) {
accepted := map[string]string{
"https://www.example.com/blog": "https://www.example.com",
"http://www.example.com": "https://www.example.com",
"http://localhost:5173": "http://localhost:5173",
"http://[::1]:8080/x": "http://[::1]:8080",
"https://user@www.example.com": "https://www.example.com",
}
for sent, stored := range accepted {
if got, err := site.SeedBaseURL(sent); err != nil || got != stored {
t.Errorf("%q gave %q, %v; expected %q", sent, got, err, stored)
}
}
for _, sent := range []string{"", "://not a url", "ftp://example.com", "example.com"} {
if _, err := site.SeedBaseURL(sent); err == nil {
t.Errorf("%q must be refused", sent)
}
}
})
t.Run("NormalizeLocale keeps valid tags and turns empty into none", func(t *testing.T) {
for _, locale := range []string{"en", "fr-CA", "zh-Hant-TW", "yue"} {
if got, err := site.NormalizeLocale(&locale); err != nil || got == nil || *got != locale {
t.Errorf("%q gave %v, %v", locale, got, err)
}
}
empty := ""
if got, err := site.NormalizeLocale(&empty); err != nil || got != nil {
t.Errorf("empty gave %v, %v", got, err)
}
if got, err := site.NormalizeLocale(nil); err != nil || got != nil {
t.Errorf("nil gave %v, %v", got, err)
}
for _, locale := range []string{"english", "fr_FR", "f", "FR", "fr-"} {
if _, err := site.NormalizeLocale(&locale); !errors.Is(err, site.ErrInvalidLocale) {
t.Errorf("%q must be refused", locale)
}
}
})
t.Run("IsValidKey enforces the pattern and the length", func(t *testing.T) {
valid := []string{"ab", "shop", "my-shop-2", "a1", strings.Repeat("a", 32)}
invalid := []string{"", "a", "Shop", "-shop", "shop-", "my--shop", "my_shop", strings.Repeat("a", 33)}
for _, key := range valid {
if !site.IsValidKey(key) {
t.Errorf("%q must be valid", key)
}
}
for _, key := range invalid {
if site.IsValidKey(key) {
t.Errorf("%q must be invalid", key)
}
}
})
}
func TestSiteUseCaseRules(t *testing.T) {
t.Run("Creating an article assigns it to the default site", func(t *testing.T) {
repo := newMockArticleRepo()
created, err := useCases.NewCreateArticleUseCase(repo, &fakeBodyRenderer{}, discardRecorder, plainSites()).CreateArticle(testCtx, useCases.CreateArticleCommand{Title: "Valid Title"})
if err != nil || created.SiteID != 1 {
t.Errorf("created %+v, %v", created, err)
}
})
t.Run("Creating an article fails when no default site can be read", func(t *testing.T) {
repo := newMockArticleRepo()
sites := newMockSiteRepo(repo)
uc := useCases.NewCreateArticleUseCase(repo, &fakeBodyRenderer{}, discardRecorder, sites)
_, err := uc.CreateArticle(testCtx, useCases.CreateArticleCommand{Title: "Valid Title"})
if !errors.Is(err, site.ErrSiteNotFound) || repo.createCalled {
t.Errorf("err = %v, create called = %v", err, repo.createCalled)
}
})
t.Run("A destination site that does not exist leaves the article untouched", func(t *testing.T) {
repo := newMockArticleRepo()
repo.articles[1] = article.Article{ID: 1, Title: "Valid Title", SiteID: 1}
sites := newMockSiteRepo(repo)
sites.seed(1, "Main", "main", true)
uc := useCases.NewUpdateArticleUseCase(repo, &fakeBodyRenderer{}, discardRecorder, sites)
missing := uint32(9)
_, err := uc.Update(testCtx, 1, useCases.UpdateArticleCommand{SiteID: &missing})
if !errors.Is(err, site.ErrSiteNotFound) || repo.articles[1].SiteID != 1 {
t.Errorf("err = %v, stored %+v", err, repo.articles[1])
}
})
t.Run("Deleting a site validates the destination before touching anything", func(t *testing.T) {
repo := newMockArticleRepo()
sites := newMockSiteRepo(repo)
sites.seed(1, "Main", "main", true)
sites.seed(2, "Shop", "shop", false)
repo.articles[1] = article.Article{ID: 1, Title: "Valid Title", SiteID: 2}
uc := useCases.NewDeleteSiteUseCase(sites, discardRecorder)
self := uint32(2)
for name, command := range map[string]useCases.DeleteSiteCommand{
"itself": {ID: 2, ReassignTo: &self},
"invalid": {ID: 2, ReassignInvalid: true},
"zero": {ID: 2, ReassignTo: idPointer(0)},
} {
if err := uc.Delete(testCtx, command); !errors.Is(err, site.ErrInvalidReassign) {
t.Errorf("%s: err = %v", name, err)
}
}
if sites.deleteCalls != 0 {
t.Errorf("the repository was called %d times", sites.deleteCalls)
}
})
t.Run("Editing a site with is_default is refused", func(t *testing.T) {
repo := newMockArticleRepo()
sites := newMockSiteRepo(repo)
sites.seed(2, "Shop", "shop", false)
uc := useCases.NewUpdateSiteUseCase(sites, newMockImageRepo(), discardRecorder)
flag := true
_, err := uc.Update(testCtx, 2, site.Patch{IsDefault: &flag})
if !errors.Is(err, site.ErrUseSetDefault) {
t.Errorf("err = %v", err)
}
})
}