mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
API: Improve cluster theme endpoint and tests #98
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -66,10 +66,19 @@ func ClusterGetTheme(router *gin.RouterGroup) {
|
||||
event.AuditDebug([]string{clientIp, "session %s", string(acl.ResourceCluster), "theme", "download", "theme path not found"}, s.RefID)
|
||||
AbortNotFound(c)
|
||||
return
|
||||
} else {
|
||||
event.AuditDebug([]string{clientIp, "session %s", string(acl.ResourceCluster), "theme", "download", "creating theme archive from %s"}, s.RefID, clean.Log(themePath))
|
||||
}
|
||||
|
||||
// Require a non-empty app.js file to avoid distributing empty themes.
|
||||
// This aligns with bootstrap behavior, which only installs a theme when
|
||||
// app.js exists locally or can be fetched from the Portal.
|
||||
if !fs.FileExistsNotEmpty(filepath.Join(themePath, "app.js")) {
|
||||
event.AuditDebug([]string{clientIp, "session %s", string(acl.ResourceCluster), "theme", "download", "app.js missing or empty"}, s.RefID)
|
||||
AbortNotFound(c)
|
||||
return
|
||||
}
|
||||
|
||||
event.AuditDebug([]string{clientIp, "session %s", string(acl.ResourceCluster), "theme", "download", "creating theme archive from %s"}, s.RefID, clean.Log(themePath))
|
||||
|
||||
// Add response headers.
|
||||
AddDownloadHeader(c, "theme.zip")
|
||||
AddContentTypeHeader(c, header.ContentTypeZip)
|
||||
|
||||
@@ -58,6 +58,7 @@ func TestClusterGetTheme(t *testing.T) {
|
||||
|
||||
assert.NoError(t, os.MkdirAll(filepath.Join(tempTheme, "sub"), 0o755))
|
||||
// Visible files
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(tempTheme, "app.js"), []byte("console.log('ok')\n"), 0o644))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(tempTheme, "style.css"), []byte("body{}\n"), 0o644))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(tempTheme, "sub", "visible.txt"), []byte("ok\n"), 0o644))
|
||||
// Hidden file
|
||||
@@ -112,22 +113,15 @@ func TestClusterGetTheme(t *testing.T) {
|
||||
defer func() { _ = os.RemoveAll(tempTheme) }()
|
||||
conf.SetThemePath(tempTheme)
|
||||
|
||||
// Hidden-only content to ensure exclusion yields empty archive.
|
||||
// Hidden-only content and no app.js should yield 404.
|
||||
assert.NoError(t, os.MkdirAll(filepath.Join(tempTheme, ".hidden-dir"), 0o755))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(tempTheme, ".hidden-dir", "file.txt"), []byte("secret\n"), 0o644))
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(tempTheme, ".hidden"), []byte("secret\n"), 0o644))
|
||||
|
||||
r := PerformRequest(app, http.MethodGet, "/api/v1/cluster/theme")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
||||
// Verify headers
|
||||
assert.Equal(t, header.ContentTypeZip, r.Header().Get(header.ContentType))
|
||||
assert.Contains(t, r.Header().Get(header.ContentDisposition), "attachment; filename=theme.zip")
|
||||
|
||||
// Verify zip is valid and empty (no files included)
|
||||
body := r.Body.Bytes()
|
||||
zr, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(zr.File))
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/cluster/theme", nil)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
app.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user