mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Tests: Add unit tests for recent metadata, auth, and config changes #5697
This commit is contained in:
parent
9fe532914d
commit
b2b1785126
5 changed files with 120 additions and 4 deletions
|
|
@ -165,6 +165,35 @@ describe("common/config", () => {
|
|||
expect(config.getIcon()).toBe("/i/pro-1/_theme/logo.svg");
|
||||
});
|
||||
|
||||
it("getIcon resolves a built-in appIcon name against the static uri", () => {
|
||||
const config = new Config(new StorageShim(), { siteTitle: "Foo", baseUri: "/i/pro-1", appIcon: "bloom" });
|
||||
expect(config.getIcon()).toBe("/i/pro-1/static/icons/bloom.svg");
|
||||
});
|
||||
|
||||
it("getIcon resolves each selectable app-icon variant to its static svg", () => {
|
||||
["crisp", "mint", "bold", "bloom", "flower", "ring", "shutter"].forEach((name) => {
|
||||
const config = new Config(new StorageShim(), { siteTitle: "Foo", appIcon: name });
|
||||
expect(config.getIcon()).toBe(`/static/icons/${name}.svg`);
|
||||
});
|
||||
});
|
||||
|
||||
it("getIcon falls back to the logo for an unknown or empty app-icon name", () => {
|
||||
expect(new Config(new StorageShim(), { siteTitle: "Foo", appIcon: "does-not-exist" }).getIcon()).toBe("/static/icons/logo.svg");
|
||||
expect(new Config(new StorageShim(), { siteTitle: "Foo" }).getIcon()).toBe("/static/icons/logo.svg");
|
||||
});
|
||||
|
||||
it("getIcon prefers a theme-provided icon over the appIcon setting", () => {
|
||||
const config = new Config(new StorageShim(), {
|
||||
siteTitle: "Foo",
|
||||
baseUri: "/i/pro-1",
|
||||
appIcon: "bloom",
|
||||
settings: { ui: { theme: "branded-icon" } },
|
||||
});
|
||||
themes.Set("branded-icon", { name: "branded-icon", title: "Branded", colors: {}, variables: { icon: "logo.svg" } });
|
||||
config.setTheme("branded-icon");
|
||||
expect(config.getIcon()).toBe("/i/pro-1/_theme/logo.svg");
|
||||
});
|
||||
|
||||
it("should store values", () => {
|
||||
const storage = new StorageShim();
|
||||
const values = { siteTitle: "Foo", country: "Germany", city: "Hamburg" };
|
||||
|
|
|
|||
|
|
@ -142,6 +142,37 @@ func TestAuthAny_AppPasswordsDisabled(t *testing.T) {
|
|||
assert.Equal(t, http.StatusForbidden, s2.HttpStatus())
|
||||
})
|
||||
}
|
||||
|
||||
// Negative control: the gate keys on IsApplication(), so a non-application
|
||||
// session (here a client-credentials grant) must keep authorizing whether or
|
||||
// not app passwords are disabled, proving the flag does not over-reject.
|
||||
t.Run("NonApplicationSessionUnaffected", func(t *testing.T) {
|
||||
sess, err := entity.AddClientSession("alice-client-cred", conf.SessionMaxAge(), "*", authn.GrantClientCredentials, nil)
|
||||
require.NoError(t, err)
|
||||
require.False(t, sess.IsApplication())
|
||||
token := sess.AuthToken()
|
||||
|
||||
authPhotos := func() *entity.Session {
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/v1/photos", nil)
|
||||
header.SetAuthorization(req, token)
|
||||
req.RemoteAddr = "10.9.8.7:4321"
|
||||
c.Request = req
|
||||
return AuthAny(c, acl.ResourcePhotos, acl.Permissions{acl.ActionView})
|
||||
}
|
||||
|
||||
conf.Settings().Features.AppPasswords = true
|
||||
s := authPhotos()
|
||||
require.NotNil(t, s)
|
||||
assert.Equal(t, http.StatusOK, s.HttpStatus())
|
||||
|
||||
conf.Settings().Features.AppPasswords = false
|
||||
s2 := authPhotos()
|
||||
require.NotNil(t, s2)
|
||||
assert.Equal(t, http.StatusOK, s2.HttpStatus(), "disabling app passwords must not reject non-application sessions")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthAny_AppPasswordWebLoginDisabled(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package api
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -94,6 +96,32 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSearchFolders_Headers(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
_ = conf.CreateDirectories()
|
||||
SearchFoldersOriginals(router)
|
||||
|
||||
// Request uncached results so the count headers are always written
|
||||
// (a cache hit returns early before the headers are added).
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/originals?uncached=true")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
result := r.Result()
|
||||
|
||||
xFiles, err := strconv.Atoi(result.Header.Get("X-Files"))
|
||||
assert.NoError(t, err, "X-Files header should be an integer")
|
||||
xFolders, err := strconv.Atoi(result.Header.Get("X-Folders"))
|
||||
assert.NoError(t, err, "X-Folders header should be an integer")
|
||||
xCount, err := strconv.Atoi(result.Header.Get("X-Count"))
|
||||
assert.NoError(t, err, "X-Count header should be an integer")
|
||||
|
||||
// X-Count is the combined number of files and folders returned.
|
||||
assert.Equal(t, xFiles+xFolders, xCount)
|
||||
|
||||
// An unpaginated request reports the form's zero-value count and offset.
|
||||
assert.Equal(t, "0", result.Header.Get("X-Limit"))
|
||||
assert.Equal(t, "0", result.Header.Get("X-Offset"))
|
||||
}
|
||||
|
||||
func TestGetFoldersImport(t *testing.T) {
|
||||
t.Run("Flat", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package entity
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -1645,10 +1647,21 @@ func TestPhoto_ArchiveRestore(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPhoto_SetCameraSerial(t *testing.T) {
|
||||
m := &Photo{}
|
||||
assert.Empty(t, m.CameraSerial)
|
||||
m.SetCameraSerial("abcCamera")
|
||||
assert.Equal(t, "abcCamera", m.CameraSerial)
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
m := &Photo{}
|
||||
assert.Empty(t, m.CameraSerial)
|
||||
m.SetCameraSerial("abcCamera")
|
||||
assert.Equal(t, "abcCamera", m.CameraSerial)
|
||||
})
|
||||
t.Run("MultiByteClippedOnRuneBoundary", func(t *testing.T) {
|
||||
// camera_serial is VARBINARY(160), so a long multi-byte value must be byte-clipped
|
||||
// without splitting a rune, keeping the stored value within budget and valid UTF-8.
|
||||
m := &Photo{}
|
||||
m.SetCameraSerial(strings.Repeat("世", 100)) // 100 runes x 3 bytes = 300 bytes.
|
||||
assert.NotEmpty(t, m.CameraSerial)
|
||||
assert.LessOrEqual(t, len(m.CameraSerial), txt.ClipDefault)
|
||||
assert.True(t, utf8.ValidString(m.CameraSerial))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPhoto_MapKey(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -27,3 +27,18 @@ func TestNewLens(t *testing.T) {
|
|||
assert.Equal(t, "New Model", result.LensModel)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLens_Validate(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
frm := &Lens{LensMake: "Canon", LensModel: "EF 50mm f/1.8"}
|
||||
assert.NoError(t, frm.Validate())
|
||||
})
|
||||
t.Run("EmptyMake", func(t *testing.T) {
|
||||
frm := &Lens{LensMake: "", LensModel: "EF 50mm f/1.8"}
|
||||
assert.Error(t, frm.Validate())
|
||||
})
|
||||
t.Run("EmptyModel", func(t *testing.T) {
|
||||
frm := &Lens{LensMake: "Canon", LensModel: ""}
|
||||
assert.Error(t, frm.Validate())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue