mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 20:49:02 +00:00
Merge pull request #339 from photoview/integration-tests
Integration tests
This commit is contained in:
2
.github/mysql.testing.env
vendored
Normal file
2
.github/mysql.testing.env
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
PHOTOVIEW_DATABASE_DRIVER=mysql
|
||||
PHOTOVIEW_MYSQL_URL='photoview:photosecret@tcp(127.0.0.1)/photoview_test'
|
||||
2
.github/postgres.testing.env
vendored
Normal file
2
.github/postgres.testing.env
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
PHOTOVIEW_DATABASE_DRIVER=postgres
|
||||
PHOTOVIEW_POSTGRES_URL=postgres://photoview:photosecret@127.0.0.1/photoview_test
|
||||
2
.github/sqlite.testing.env
vendored
Normal file
2
.github/sqlite.testing.env
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
PHOTOVIEW_DATABASE_DRIVER=sqlite
|
||||
PHOTOVIEW_SQLITE_PATH=photoview_test.db
|
||||
100
.github/workflows/tests.yml
vendored
100
.github/workflows/tests.yml
vendored
@@ -11,20 +11,62 @@ jobs:
|
||||
name: Test API
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
database: ['mysql', 'postgres', 'sqlite']
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.5
|
||||
env:
|
||||
MYSQL_DATABASE: photoview_test
|
||||
MYSQL_USER: photoview
|
||||
MYSQL_PASSWORD: photosecret
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: yes
|
||||
options: >-
|
||||
--health-cmd="mysqladmin ping"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
postgres:
|
||||
image: postgres:13.2
|
||||
env:
|
||||
POSTGRES_USER: photoview
|
||||
POSTGRES_PASSWORD: photosecret
|
||||
POSTGRES_DB: photoview_test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: api
|
||||
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Go dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Get C dependencies
|
||||
run: |
|
||||
sudo add-apt-repository ppa:strukturag/libheif
|
||||
@@ -43,8 +85,29 @@ jobs:
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
|
||||
- name: Configure MySQL
|
||||
if: ${{ matrix.database == 'mysql' }}
|
||||
run: |
|
||||
cp ../.github/mysql.testing.env testing.env
|
||||
|
||||
- name: Configure Postgres
|
||||
if: ${{ matrix.database == 'postgres' }}
|
||||
run: |
|
||||
cp ../.github/postgres.testing.env testing.env
|
||||
|
||||
- name: Configure Sqlite
|
||||
if: ${{ matrix.database == 'sqlite' }}
|
||||
run: |
|
||||
touch photoview_test.db
|
||||
cp ../.github/sqlite.testing.env testing.env
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
run: go test ./... -v -database -filesystem -p 1 -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
flags: api
|
||||
|
||||
test-ui:
|
||||
name: Test UI
|
||||
@@ -59,15 +122,28 @@ jobs:
|
||||
node-version: [15.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Cache NPM dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm clean-install
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
- name: Install dependencies
|
||||
run: npm clean-install
|
||||
|
||||
- name: Test
|
||||
run: npm run test:ci
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
flags: ui
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ photoview.db
|
||||
photoview.db-journal
|
||||
|
||||
.env
|
||||
testing.env
|
||||
|
||||
# docker
|
||||
docker-compose.yml
|
||||
|
||||
@@ -20,8 +20,7 @@ import (
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func getMysqlAddress() (string, error) {
|
||||
addressString := utils.EnvMysqlURL.GetValue()
|
||||
func GetMysqlAddress(addressString string) (string, error) {
|
||||
if addressString == "" {
|
||||
return "", errors.New(fmt.Sprintf("Environment variable %s missing, exiting", utils.EnvMysqlURL.GetName()))
|
||||
}
|
||||
@@ -37,8 +36,7 @@ func getMysqlAddress() (string, error) {
|
||||
return config.FormatDSN(), nil
|
||||
}
|
||||
|
||||
func getPostgresAddress() (*url.URL, error) {
|
||||
addressString := utils.EnvPostgresURL.GetValue()
|
||||
func GetPostgresAddress(addressString string) (*url.URL, error) {
|
||||
if addressString == "" {
|
||||
return nil, errors.New(fmt.Sprintf("Environment variable %s missing, exiting", utils.EnvPostgresURL.GetName()))
|
||||
}
|
||||
@@ -51,8 +49,7 @@ func getPostgresAddress() (*url.URL, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func getSqliteAddress() (*url.URL, error) {
|
||||
path := utils.EnvSqlitePath.GetValue()
|
||||
func GetSqliteAddress(path string) (*url.URL, error) {
|
||||
if path == "" {
|
||||
path = "photoview.db"
|
||||
}
|
||||
@@ -73,11 +70,11 @@ func getSqliteAddress() (*url.URL, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func configureDatabase(config *gorm.Config) (*gorm.DB, error) {
|
||||
func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
|
||||
var databaseDialect gorm.Dialector
|
||||
switch drivers.DatabaseDriver() {
|
||||
case drivers.DatabaseDriverMysql:
|
||||
mysqlAddress, err := getMysqlAddress()
|
||||
mysqlAddress, err := GetMysqlAddress(utils.EnvMysqlURL.GetValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -85,7 +82,7 @@ func configureDatabase(config *gorm.Config) (*gorm.DB, error) {
|
||||
databaseDialect = gorm_mysql.Open(mysqlAddress)
|
||||
|
||||
case drivers.DatabaseDriverSqlite:
|
||||
sqliteAddress, err := getSqliteAddress()
|
||||
sqliteAddress, err := GetSqliteAddress(utils.EnvSqlitePath.GetValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,7 +90,7 @@ func configureDatabase(config *gorm.Config) (*gorm.DB, error) {
|
||||
databaseDialect = sqlite.Open(sqliteAddress.String())
|
||||
|
||||
case drivers.DatabaseDriverPostgres:
|
||||
postgresAddress, err := getPostgresAddress()
|
||||
postgresAddress, err := GetPostgresAddress(utils.EnvPostgresURL.GetValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -126,7 +123,7 @@ func SetupDatabase() (*gorm.DB, error) {
|
||||
for retryCount := 1; retryCount <= 5; retryCount++ {
|
||||
|
||||
var err error
|
||||
db, err = configureDatabase(&config)
|
||||
db, err = ConfigureDatabase(&config)
|
||||
if err == nil {
|
||||
sqlDB, dbErr := db.DB()
|
||||
if dbErr != nil {
|
||||
@@ -151,24 +148,26 @@ func SetupDatabase() (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func MigrateDatabase(db *gorm.DB) error {
|
||||
err := db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.AccessToken{},
|
||||
&models.SiteInfo{},
|
||||
&models.Media{},
|
||||
&models.MediaURL{},
|
||||
&models.Album{},
|
||||
&models.MediaEXIF{},
|
||||
&models.VideoMetadata{},
|
||||
&models.ShareToken{},
|
||||
&models.UserMediaData{},
|
||||
&models.UserPreferences{},
|
||||
var database_models []interface{} = []interface{}{
|
||||
&models.User{},
|
||||
&models.AccessToken{},
|
||||
&models.SiteInfo{},
|
||||
&models.Media{},
|
||||
&models.MediaURL{},
|
||||
&models.Album{},
|
||||
&models.MediaEXIF{},
|
||||
&models.VideoMetadata{},
|
||||
&models.ShareToken{},
|
||||
&models.UserMediaData{},
|
||||
&models.UserPreferences{},
|
||||
|
||||
// Face detection
|
||||
&models.FaceGroup{},
|
||||
&models.ImageFace{},
|
||||
)
|
||||
// Face detection
|
||||
&models.FaceGroup{},
|
||||
&models.ImageFace{},
|
||||
}
|
||||
|
||||
func MigrateDatabase(db *gorm.DB) error {
|
||||
err := db.AutoMigrate(database_models...)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Auto migration failed: %v\n", err)
|
||||
@@ -188,3 +187,22 @@ func MigrateDatabase(db *gorm.DB) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClearDatabase(db *gorm.DB) error {
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
sess := tx.Session(&gorm.Session{AllowGlobalUpdate: true})
|
||||
for _, model := range database_models {
|
||||
if err := sess.Delete(model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/strukturag/libheif v1.11.0
|
||||
github.com/vektah/gqlparser/v2 v2.1.0
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
@@ -30,6 +32,7 @@ require (
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.0.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gorm.io/driver/mysql v1.0.5
|
||||
gorm.io/driver/postgres v1.0.8
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
|
||||
@@ -539,6 +539,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -546,6 +548,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/strukturag/libheif v1.11.0 h1:HaWu5re98INSXNq7C8o5AwLcv2qD8+U7a+jVCpGWemI=
|
||||
github.com/strukturag/libheif v1.11.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
|
||||
@@ -756,6 +760,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
|
||||
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
|
||||
gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=
|
||||
|
||||
93
api/graphql/models/album_test.go
Normal file
93
api/graphql/models/album_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAlbumGetChildren(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
rootAlbum := models.Album{
|
||||
Title: "root",
|
||||
Path: "/photos",
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&rootAlbum).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
children := []models.Album{
|
||||
{
|
||||
Title: "child1",
|
||||
Path: "/photos/child1",
|
||||
ParentAlbumID: &rootAlbum.ID,
|
||||
},
|
||||
{
|
||||
Title: "child2",
|
||||
Path: "/photos/child2",
|
||||
ParentAlbumID: &rootAlbum.ID,
|
||||
},
|
||||
{
|
||||
Title: "not_child",
|
||||
Path: "/videos",
|
||||
},
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&children).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
sub_child := models.Album{
|
||||
Title: "subchild",
|
||||
Path: "/photos/child1/subchild",
|
||||
ParentAlbumID: &children[0].ID,
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&sub_child).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
root_children, err := rootAlbum.GetChildren(db, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
expected_children := []*models.Album{
|
||||
{
|
||||
Title: "root",
|
||||
Path: "/photos",
|
||||
},
|
||||
{
|
||||
Title: "child1",
|
||||
Path: "/photos/child1",
|
||||
},
|
||||
{
|
||||
Title: "child2",
|
||||
Path: "/photos/child2",
|
||||
},
|
||||
{
|
||||
Title: "subchild",
|
||||
Path: "/photos/child1/subchild",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, len(expected_children), len(root_children))
|
||||
|
||||
for _, expected := range expected_children {
|
||||
found_expected := false
|
||||
for _, item := range root_children {
|
||||
if item.Title == expected.Title && item.Path == expected.Path {
|
||||
found_expected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found_expected {
|
||||
assert.Failf(t, "root children did not match", "expected to find item: %v", expected)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package models
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSanitizeMediaName(t *testing.T) {
|
||||
@@ -12,9 +16,36 @@ func TestSanitizeMediaName(t *testing.T) {
|
||||
{"..\\/", "__"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if SanitizeMediaName(test[0]) != test[1] {
|
||||
t.Errorf("SanitizeMediaName test %d failed: got '%s', expected '%s'", i, test[1], SanitizeMediaName(test[0]))
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("sanitize: %s", test[0]), func(t *testing.T) {
|
||||
assert.Equal(t, test[1], models.SanitizeMediaName(test[0]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaURLCachePath(t *testing.T) {
|
||||
mediaUrl := models.MediaURL{}
|
||||
mediaUrl.Media = nil
|
||||
|
||||
_, err := mediaUrl.CachedPath()
|
||||
assert.EqualError(t, err, "mediaURL.Media is nil")
|
||||
|
||||
mediaUrl = models.MediaURL{
|
||||
Purpose: models.PhotoThumbnail,
|
||||
MediaID: 1,
|
||||
Media: &models.Media{
|
||||
Model: models.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Title: "media.jpg",
|
||||
AlbumID: 2,
|
||||
},
|
||||
MediaName: "media_thumb.jpg",
|
||||
}
|
||||
|
||||
path, err := mediaUrl.CachedPath()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "media_cache/2/1/media_thumb.jpg", path)
|
||||
|
||||
}
|
||||
|
||||
12
api/graphql/models/models_test.go
Normal file
12
api/graphql/models/models_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(test_utils.IntegrationTestRun(m))
|
||||
}
|
||||
@@ -16,32 +16,37 @@ func (SiteInfo) TableName() string {
|
||||
return "site_info"
|
||||
}
|
||||
|
||||
func DefaultSiteInfo() SiteInfo {
|
||||
defaultConcurrentWorkers := 3
|
||||
if db_drivers.DatabaseDriver() == db_drivers.DatabaseDriverSqlite {
|
||||
defaultConcurrentWorkers = 1
|
||||
}
|
||||
|
||||
return SiteInfo{
|
||||
InitialSetup: true,
|
||||
PeriodicScanInterval: 0,
|
||||
ConcurrentWorkers: defaultConcurrentWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSiteInfo gets the site info row from the database, and creates it if it does not exist
|
||||
func GetSiteInfo(db *gorm.DB) (*SiteInfo, error) {
|
||||
|
||||
var siteInfo SiteInfo
|
||||
var siteInfo []*SiteInfo
|
||||
|
||||
if err := db.First(&siteInfo).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
|
||||
defaultConcurrentWorkers := 3
|
||||
if db_drivers.DatabaseDriver() == db_drivers.DatabaseDriverSqlite {
|
||||
defaultConcurrentWorkers = 1
|
||||
}
|
||||
|
||||
siteInfo = SiteInfo{
|
||||
InitialSetup: true,
|
||||
PeriodicScanInterval: 0,
|
||||
ConcurrentWorkers: defaultConcurrentWorkers,
|
||||
}
|
||||
|
||||
if err := db.Create(&siteInfo).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "initialize site_info")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "get site info from database")
|
||||
}
|
||||
if err := db.Limit(1).Find(&siteInfo).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "get site info from database")
|
||||
}
|
||||
|
||||
return &siteInfo, nil
|
||||
if len(siteInfo) == 0 {
|
||||
newSiteInfo := DefaultSiteInfo()
|
||||
|
||||
if err := db.Create(&newSiteInfo).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "initialize site_info")
|
||||
}
|
||||
|
||||
return &newSiteInfo, nil
|
||||
} else {
|
||||
return siteInfo[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
41
api/graphql/models/site_info_test.go
Normal file
41
api/graphql/models/site_info_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestSiteInfo(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
site_info, err := models.GetSiteInfo(db)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, models.DefaultSiteInfo(), *site_info)
|
||||
|
||||
site_info.InitialSetup = false
|
||||
site_info.PeriodicScanInterval = 360
|
||||
site_info.ConcurrentWorkers = 10
|
||||
|
||||
if !assert.NoError(t, db.Session(&gorm.Session{AllowGlobalUpdate: true}).Save(&site_info).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
site_info, err = models.GetSiteInfo(db)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, models.SiteInfo{
|
||||
InitialSetup: false,
|
||||
PeriodicScanInterval: 360,
|
||||
ConcurrentWorkers: 10,
|
||||
}, *site_info)
|
||||
|
||||
}
|
||||
@@ -108,14 +108,9 @@ func ValidRootPath(rootPath string) bool {
|
||||
}
|
||||
|
||||
func RegisterUser(db *gorm.DB, username string, password *string, admin bool) (*User, error) {
|
||||
// if !ValidRootPath(rootPath) {
|
||||
// return nil, ErrorInvalidRootPath
|
||||
// }
|
||||
|
||||
user := User{
|
||||
Username: username,
|
||||
// RootPath: rootPath,
|
||||
Admin: admin,
|
||||
Admin: admin,
|
||||
}
|
||||
|
||||
if password != nil {
|
||||
@@ -148,11 +143,6 @@ func (user *User) GenerateAccessToken(db *gorm.DB) (*AccessToken, error) {
|
||||
|
||||
token_value := string(bytes)
|
||||
expire := time.Now().Add(14 * 24 * time.Hour)
|
||||
// expireString := expire.UTC().Format("2006-01-02 15:04:05")
|
||||
|
||||
// if _, err := database.Exec("INSERT INTO access_token (value, expire, user_id) VALUES (?, ?, ?)", token_value, expireString, user.UserID); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
token := AccessToken{
|
||||
UserID: user.ID,
|
||||
@@ -183,14 +173,11 @@ func (user *User) FillAlbums(db *gorm.DB) error {
|
||||
}
|
||||
|
||||
func (user *User) OwnsAlbum(db *gorm.DB, album *Album) (bool, error) {
|
||||
|
||||
// user.QueryUserAlbums(db, db.Where("id = ?", album.ID))
|
||||
|
||||
// TODO: Implement this
|
||||
return true, nil
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (user *User) OwnsMedia(db *gorm.DB, media *Media) (bool, error) {
|
||||
// TODO: implement this
|
||||
return true, nil
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
60
api/graphql/models/user_test.go
Normal file
60
api/graphql/models/user_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserRegistrationAuthorization(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
password := "1234"
|
||||
user, err := models.RegisterUser(db, "admin", &password, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(t, user)
|
||||
assert.EqualValues(t, "admin", user.Username)
|
||||
assert.NotNil(t, user.Password)
|
||||
assert.NotEqualValues(t, "1234", user.Password) // should be hashed
|
||||
assert.True(t, user.Admin)
|
||||
|
||||
user, err = models.AuthorizeUser(db, "admin", "1234")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(t, user)
|
||||
assert.EqualValues(t, "admin", user.Username)
|
||||
|
||||
}
|
||||
|
||||
func TestAccessToken(t *testing.T) {
|
||||
db := test_utils.DatabaseTest(t)
|
||||
|
||||
pass := "<hashed_password>"
|
||||
user := models.User{
|
||||
Username: "user1",
|
||||
Password: &pass,
|
||||
Admin: false,
|
||||
}
|
||||
|
||||
if !assert.NoError(t, db.Save(&user).Error) {
|
||||
return
|
||||
}
|
||||
|
||||
access_token, err := user.GenerateAccessToken(db)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotNil(t, access_token)
|
||||
assert.Equal(t, user.ID, access_token.UserID)
|
||||
assert.NotEmpty(t, access_token.Value)
|
||||
assert.True(t, access_token.Expire.After(time.Now()))
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type exifParser interface {
|
||||
ParseExif(media *models.Media) (*models.MediaEXIF, error)
|
||||
ParseExif(media_path string) (*models.MediaEXIF, error)
|
||||
}
|
||||
|
||||
var use_exiftool bool = false
|
||||
@@ -55,7 +55,7 @@ func SaveEXIF(tx *gorm.DB, media *models.Media) (*models.MediaEXIF, error) {
|
||||
parser = &internalExifParser{}
|
||||
}
|
||||
|
||||
exif, err := parser.ParseExif(media)
|
||||
exif, err := parser.ParseExif(media.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse exif data")
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
type externalExifParser struct{}
|
||||
|
||||
func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
// ExifTool - No print conversion mode
|
||||
et, err := exiftool.NewExiftool(exiftool.NoPrintConversion())
|
||||
if err != nil {
|
||||
@@ -19,8 +19,9 @@ func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
}
|
||||
defer et.Close()
|
||||
|
||||
fileInfos := et.ExtractMetadata(media.Path)
|
||||
fileInfos := et.ExtractMetadata(media_path)
|
||||
newExif := models.MediaEXIF{}
|
||||
found_exif := false
|
||||
|
||||
for _, fileInfo := range fileInfos {
|
||||
if fileInfo.Err != nil {
|
||||
@@ -31,18 +32,21 @@ func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
// Get camera model
|
||||
model, err := fileInfo.GetString("Model")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Camera = &model
|
||||
}
|
||||
|
||||
// Get Camera make
|
||||
make, err := fileInfo.GetString("Make")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Maker = &make
|
||||
}
|
||||
|
||||
// Get lens
|
||||
lens, err := fileInfo.GetString("LensModel")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Lens = &lens
|
||||
}
|
||||
|
||||
@@ -52,6 +56,7 @@ func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
layout := "2006:01:02 15:04:05"
|
||||
dateTime, err := time.Parse(layout, date)
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.DateShot = &dateTime
|
||||
}
|
||||
}
|
||||
@@ -59,58 +64,71 @@ func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
// Get exposure time
|
||||
exposureTime, err := fileInfo.GetFloat("ExposureTime")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Exposure = &exposureTime
|
||||
}
|
||||
|
||||
// Get aperture
|
||||
aperture, err := fileInfo.GetFloat("Aperture")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Aperture = &aperture
|
||||
}
|
||||
|
||||
// Get ISO
|
||||
iso, err := fileInfo.GetInt("ISO")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Iso = &iso
|
||||
}
|
||||
|
||||
// Get focal length
|
||||
focalLen, err := fileInfo.GetFloat("FocalLength")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.FocalLength = &focalLen
|
||||
}
|
||||
|
||||
// Get flash info
|
||||
flash, err := fileInfo.GetInt("Flash")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Flash = &flash
|
||||
}
|
||||
|
||||
// Get orientation
|
||||
orientation, err := fileInfo.GetInt("Orientation")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.Orientation = &orientation
|
||||
}
|
||||
|
||||
// Get exposure program
|
||||
expProgram, err := fileInfo.GetInt("ExposureProgram")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.ExposureProgram = &expProgram
|
||||
}
|
||||
|
||||
// GPS coordinates - longitude
|
||||
longitudeRaw, err := fileInfo.GetFloat("GPSLongitude")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.GPSLongitude = &longitudeRaw
|
||||
}
|
||||
|
||||
// GPS coordinates - latitude
|
||||
latitudeRaw, err := fileInfo.GetFloat("GPSLatitude")
|
||||
if err == nil {
|
||||
found_exif = true
|
||||
newExif.GPSLatitude = &latitudeRaw
|
||||
}
|
||||
}
|
||||
|
||||
if !found_exif {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
returnExif = &newExif
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/pkg/errors"
|
||||
@@ -15,8 +16,8 @@ import (
|
||||
// internalExifParser is an exif parser that parses the media without the use of external tools
|
||||
type internalExifParser struct{}
|
||||
|
||||
func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
photoFile, err := os.Open(media.Path)
|
||||
func (p *internalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
photoFile, err := os.Open(media_path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,29 +34,32 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
|
||||
exifTags, err := exif.Decode(photoFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Could not decode EXIF")
|
||||
return nil, nil
|
||||
// return nil, errors.Wrap(err, "Could not decode EXIF")
|
||||
}
|
||||
|
||||
newExif := models.MediaEXIF{}
|
||||
|
||||
model, err := p.readStringTag(exifTags, exif.Model, media)
|
||||
model, err := p.readStringTag(exifTags, exif.Model, media_path)
|
||||
if err == nil {
|
||||
newExif.Camera = model
|
||||
}
|
||||
|
||||
maker, err := p.readStringTag(exifTags, exif.Make, media)
|
||||
maker, err := p.readStringTag(exifTags, exif.Make, media_path)
|
||||
if err == nil {
|
||||
newExif.Maker = maker
|
||||
}
|
||||
|
||||
lens, err := p.readStringTag(exifTags, exif.LensModel, media)
|
||||
lens, err := p.readStringTag(exifTags, exif.LensModel, media_path)
|
||||
if err == nil {
|
||||
newExif.Lens = lens
|
||||
}
|
||||
|
||||
date, err := exifTags.DateTime()
|
||||
if err == nil {
|
||||
newExif.DateShot = &date
|
||||
_, tz := date.Zone()
|
||||
date_utc := date.Add(time.Duration(tz) * time.Second).UTC()
|
||||
newExif.DateShot = &date_utc
|
||||
}
|
||||
|
||||
exposure, err := exifTags.Get(exif.ExposureTime)
|
||||
@@ -66,7 +70,7 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
}
|
||||
}
|
||||
|
||||
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media)
|
||||
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media_path)
|
||||
if err == nil {
|
||||
aperture, _ := apertureRat.Float64()
|
||||
newExif.Aperture = &aperture
|
||||
@@ -74,11 +78,11 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
|
||||
isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %s\n", media.Title)
|
||||
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %v\n", media_path)
|
||||
} else {
|
||||
iso, err := isoTag.Int(0)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %s\n", media.Title)
|
||||
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %v\n", media_path)
|
||||
} else {
|
||||
iso64 := int64(iso)
|
||||
newExif.Iso = &iso64
|
||||
@@ -99,7 +103,7 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
if err == nil {
|
||||
focalLength, err := focalLengthTag.Int(1)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %s\n%s\n", media.Title, err)
|
||||
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %v\n%s\n", media_path, err)
|
||||
} else {
|
||||
focalLenFloat := float64(focalLength)
|
||||
newExif.FocalLength = &focalLenFloat
|
||||
@@ -108,19 +112,19 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
}
|
||||
}
|
||||
|
||||
flash, err := p.readIntegerTag(exifTags, exif.Flash, media)
|
||||
flash, err := p.readIntegerTag(exifTags, exif.Flash, media_path)
|
||||
if err == nil {
|
||||
flash64 := int64(*flash)
|
||||
newExif.Flash = &flash64
|
||||
}
|
||||
|
||||
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media)
|
||||
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media_path)
|
||||
if err == nil {
|
||||
orientation64 := int64(*orientation)
|
||||
newExif.Orientation = &orientation64
|
||||
}
|
||||
|
||||
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media)
|
||||
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media_path)
|
||||
if err == nil {
|
||||
exposureProgram64 := int64(*exposureProgram)
|
||||
newExif.ExposureProgram = &exposureProgram64
|
||||
@@ -136,59 +140,59 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
|
||||
return
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*string, error) {
|
||||
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media_path string) (*string, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.StringVal()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media_path)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*big.Rat, error) {
|
||||
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media_path string) (*big.Rat, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Rat(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media_path)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*int, error) {
|
||||
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media_path string) (*int, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media.Title)
|
||||
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media_path)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
|
||||
94
api/scanner/exif/exif_test.go
Normal file
94
api/scanner/exif/exif_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package exif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/barasher/go-exiftool"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(test_utils.IntegrationTestRun(m))
|
||||
}
|
||||
|
||||
func TestExifParsers(t *testing.T) {
|
||||
test_utils.FilesystemTest(t)
|
||||
|
||||
parsers := []struct {
|
||||
name string
|
||||
parser exifParser
|
||||
}{
|
||||
{
|
||||
name: "internal",
|
||||
parser: &internalExifParser{},
|
||||
},
|
||||
{
|
||||
name: "external",
|
||||
parser: &externalExifParser{},
|
||||
},
|
||||
}
|
||||
|
||||
images := []struct {
|
||||
path string
|
||||
assert func(t *testing.T, exif *models.MediaEXIF)
|
||||
}{
|
||||
{
|
||||
path: "./test_data/bird.jpg",
|
||||
assert: func(t *testing.T, exif *models.MediaEXIF) {
|
||||
assert.WithinDuration(t, *exif.DateShot, time.Unix(1336318784, 0).UTC(), time.Minute)
|
||||
assert.EqualValues(t, *exif.Camera, "Canon EOS 600D")
|
||||
assert.EqualValues(t, *exif.Maker, "Canon")
|
||||
assert.WithinDuration(t, *exif.DateShot, time.Unix(1336318784, 0).UTC(), time.Minute)
|
||||
assert.EqualValues(t, *exif.Aperture, 6.3)
|
||||
assert.EqualValues(t, *exif.Iso, 800)
|
||||
assert.EqualValues(t, *exif.FocalLength, 300)
|
||||
assert.EqualValues(t, *exif.Flash, 16)
|
||||
assert.EqualValues(t, *exif.Orientation, 1)
|
||||
assert.InDelta(t, *exif.GPSLatitude, 65.01681388888889, 0.0001)
|
||||
assert.InDelta(t, *exif.GPSLongitude, 25.466863888888888, 0.0001)
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "./test_data/stripped.jpg",
|
||||
assert: func(t *testing.T, exif *models.MediaEXIF) {
|
||||
assert.Nil(t, exif)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, p := range parsers {
|
||||
for _, img := range images {
|
||||
t.Run(fmt.Sprintf("%s:%s", p.name, path.Base(img.path)), func(t *testing.T) {
|
||||
|
||||
if p.name == "external" {
|
||||
_, err := exiftool.NewExiftool()
|
||||
if err != nil {
|
||||
t.Skip("failed to get exiftool, skipping test")
|
||||
}
|
||||
}
|
||||
|
||||
exif, err := p.parser.ParseExif(img.path)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
img.assert(t, exif)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func TestExternalExifParser(t *testing.T) {
|
||||
// parser := externalExifParser{}
|
||||
|
||||
// exif, err := parser.ParseExif((bird_path))
|
||||
|
||||
// if assert.NoError(t, err) {
|
||||
// assert.Equal(t, exif, &bird_exif)
|
||||
// }
|
||||
// }
|
||||
BIN
api/scanner/exif/test_data/bird.jpg
Normal file
BIN
api/scanner/exif/test_data/bird.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
api/scanner/exif/test_data/stripped.jpg
Normal file
BIN
api/scanner/exif/test_data/stripped.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
12
api/scanner/scanner_test.go
Normal file
12
api/scanner/scanner_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package scanner_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/photoview/photoview/api/test_utils"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(test_utils.UnitTestRun(m))
|
||||
}
|
||||
70
api/test_utils/integration_setup.go
Normal file
70
api/test_utils/integration_setup.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package test_utils
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type integration_options struct {
|
||||
Database *bool
|
||||
Filesystem *bool
|
||||
}
|
||||
|
||||
var integration_flags integration_options = integration_options{
|
||||
Database: flag.Bool("database", false, "run database integration tests"),
|
||||
Filesystem: flag.Bool("filesystem", false, "run filesystem integration tests"),
|
||||
}
|
||||
|
||||
var test_dbm TestDBManager = TestDBManager{}
|
||||
|
||||
func UnitTestRun(m *testing.M) int {
|
||||
flag.Parse()
|
||||
return m.Run()
|
||||
}
|
||||
|
||||
func IntegrationTestRun(m *testing.M) int {
|
||||
flag.Parse()
|
||||
|
||||
if *integration_flags.Database {
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
log.Fatal("could not get runtime file path")
|
||||
}
|
||||
|
||||
envPath := path.Join(path.Dir(file), "..", "testing.env")
|
||||
|
||||
if err := godotenv.Load(envPath); err != nil {
|
||||
log.Println("No testing.env file found")
|
||||
}
|
||||
}
|
||||
|
||||
result := m.Run()
|
||||
|
||||
test_dbm.Close()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func FilesystemTest(t *testing.T) {
|
||||
if !*integration_flags.Filesystem {
|
||||
t.Skip("Filesystem integration tests disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func DatabaseTest(t *testing.T) *gorm.DB {
|
||||
if !*integration_flags.Database {
|
||||
t.Skip("Database integration tests disabled")
|
||||
}
|
||||
|
||||
if err := test_dbm.SetupOrReset(); err != nil {
|
||||
t.Fatalf("failed to setup or reset test database: %v", err)
|
||||
}
|
||||
|
||||
return test_dbm.DB
|
||||
}
|
||||
60
api/test_utils/test_db_manager.go
Normal file
60
api/test_utils/test_db_manager.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package test_utils
|
||||
|
||||
import (
|
||||
"github.com/photoview/photoview/api/database"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TestDBManager struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func (dbm *TestDBManager) SetupOrReset() error {
|
||||
if dbm.DB == nil {
|
||||
return dbm.setup()
|
||||
} else {
|
||||
return dbm.reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (dbm *TestDBManager) Close() error {
|
||||
if dbm.DB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sqlDB, err := dbm.DB.DB()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get db instance when closing test database")
|
||||
}
|
||||
|
||||
sqlDB.Close()
|
||||
dbm.DB = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbm *TestDBManager) setup() error {
|
||||
config := gorm.Config{}
|
||||
db, err := database.ConfigureDatabase(&config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "configure test database")
|
||||
}
|
||||
|
||||
if err := database.MigrateDatabase(db); err != nil {
|
||||
return errors.Wrap(err, "migrate test database")
|
||||
}
|
||||
|
||||
dbm.DB = db
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbm *TestDBManager) reset() error {
|
||||
|
||||
if err := database.ClearDatabase(dbm.DB); err != nil {
|
||||
return errors.Wrap(err, "reset test database")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -54,10 +54,12 @@
|
||||
"start": "node --experimental-modules build.mjs watch",
|
||||
"build": "NODE_ENV=production node --experimental-modules build.mjs",
|
||||
"test": "npm run lint && npm run jest",
|
||||
"test:ci": "npm run lint && npm run jest:ci",
|
||||
"lint": "npm run lint:types & npm run lint:eslint",
|
||||
"lint:eslint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js",
|
||||
"lint:types": "tsc --noemit",
|
||||
"jest": "jest --verbose",
|
||||
"jest:ci": "jest --verbose --ci --coverage",
|
||||
"genSchemaTypes": "npx apollo client:codegen --target=typescript",
|
||||
"prepare": "(cd .. && npx husky install)"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user