BREAKING CHANGE: /sitemap.xsl is gone (404), the definitions screen and the Phase 17 settings tile are replaced by the settings singleton, and priority, change frequency, priority tiers and the update manifest are no longer written or read. The config keys golem15.sitemap.cache_seconds and golem15.sitemap.priority_tiers are removed; site_url, lastmod_ignore and rebuild_delay_seconds are new. - sitemap:import and sitemap:import-winter write the additional addresses (and site_url, default_locale) instead of the definition - the golem15_sitemap_definitions table stays in the database, unread
178 lines
5.2 KiB
Go
178 lines
5.2 KiB
Go
package sitemap_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "git.golem15.com/golem15/sm-sitemap-plugin"
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bonfire"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"git.golem15.com/golem15/summercms/modules/party"
|
|
"git.golem15.com/golem15/summercms/modules/surf"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// sitemapEnv is a booted application with the sitemap and the fake
|
|
// content plugin.
|
|
type sitemapEnv struct {
|
|
h http.Handler
|
|
app *backpack.App
|
|
gdb *gorm.DB
|
|
plugins []party.Plugin
|
|
}
|
|
|
|
// bootPlugins boots the application with the plugins ids on a fresh
|
|
// database; overrides set further config keys.
|
|
func bootPlugins(t *testing.T, ids []string, overrides ...map[string]any) *sitemapEnv {
|
|
t.Helper()
|
|
sqlDB := freshDB(t)
|
|
gdb, err := lagoon.Use(t.Context(), sqlDB)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: sitemap-smoke\nlocale: en\nfallback_locale: en\nurl: https://api.example.test\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := compass.Open(compass.Options{
|
|
Dir: dir,
|
|
Environ: []string{"SUMMER_ENV=development", "SUMMER_ADMIN__JWT__SECRET=sitemap-smoke-test-only-hs256-secret-key"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
settings := map[string]any{
|
|
"http.body_limits.default_bytes": 1048576,
|
|
"http.body_limits.upload_bytes": 8388608,
|
|
"mail.driver": "log",
|
|
"mail.from": "noreply@example.test",
|
|
}
|
|
for _, o := range overrides {
|
|
for key, value := range o {
|
|
settings[key] = value
|
|
}
|
|
}
|
|
for key, value := range settings {
|
|
if err := cfg.Set(key, value); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
app := backpack.New(cfg)
|
|
if err := lagoon.Publish(app, sqlDB, gdb); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plugins, err := party.Activate(app, ids)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := lagoon.Migrate(gdb, plugins); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h, err := surf.Assemble(app, plugins)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return &sitemapEnv{h: h, app: app, gdb: gdb, plugins: plugins}
|
|
}
|
|
|
|
func (e *sitemapEnv) get(t *testing.T, path string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
|
req.Host = "api.example.test"
|
|
rec := httptest.NewRecorder()
|
|
e.h.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|
|
|
|
func writeFile(t *testing.T, dir, name, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(dir, name)
|
|
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func testOutput() (bonfire.Output, *bytes.Buffer) {
|
|
var buf bytes.Buffer
|
|
return bonfire.NewOutput(strings.NewReader(""), &buf, &buf), &buf
|
|
}
|
|
|
|
var adminSeq atomic.Uint64
|
|
|
|
// call sends an admin API request with an optional bearer token and JSON
|
|
// body.
|
|
func (e *sitemapEnv) call(t *testing.T, method, path, token string, body any) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
var reader io.Reader
|
|
if body != nil {
|
|
raw, err := json.Marshal(body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reader = bytes.NewReader(raw)
|
|
}
|
|
req := httptest.NewRequest(method, cabana.DefaultAdminPrefix+"/api/v1"+path, reader)
|
|
req.Host = "api.example.test"
|
|
req.RemoteAddr = fmt.Sprintf("198.51.100.%d:4000", adminSeq.Add(1)%250+1)
|
|
req.Header.Set("Accept-Language", "en")
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
e.h.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|
|
|
|
// admin signs in a new administrator whose role grants permissions.
|
|
func (e *sitemapEnv) admin(t *testing.T, permissions ...string) string {
|
|
t.Helper()
|
|
stamp := fmt.Sprintf("%d%d", time.Now().UnixNano()%1_000_000_000, adminSeq.Add(1))
|
|
login, password := "admin-"+stamp, "admin-secret-"+stamp
|
|
hash, err := bouncer.HashPassword(10, password)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
grants := map[string]int{}
|
|
for _, code := range permissions {
|
|
grants[code] = 1
|
|
}
|
|
raw, _ := json.Marshal(grants)
|
|
var roleID uint
|
|
if err := e.gdb.Raw(`INSERT INTO backend_user_roles (name, code, permissions, is_system, created_at, updated_at) VALUES (?, ?, ?, FALSE, NOW(), NOW()) RETURNING id`,
|
|
"Role "+stamp, "role-"+stamp, string(raw)).Scan(&roleID).Error; err != nil || roleID == 0 {
|
|
t.Fatalf("backend role: id=%d err=%v", roleID, err)
|
|
}
|
|
if err := e.gdb.Exec(`INSERT INTO backend_users (login, email, password, is_activated, is_superuser, role_id, created_at, updated_at) VALUES (?, ?, ?, TRUE, FALSE, ?, NOW(), NOW())`,
|
|
login, login+"@example.test", hash, roleID).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rec := e.call(t, http.MethodPost, "/auth/login", "", map[string]string{"login": login, "password": password})
|
|
var body struct {
|
|
Data struct {
|
|
AccessToken string `json:"access_token"`
|
|
} `json:"data"`
|
|
}
|
|
if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &body) != nil || body.Data.AccessToken == "" {
|
|
t.Fatalf("admin login: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
return body.Data.AccessToken
|
|
}
|