diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 96bc5bcd9..3bc01c745 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -2,8 +2,11 @@ name: "Unit Tests" on: workflow_call +env: + CGO_ENABLED: 0 + jobs: - test: + test-linux: runs-on: ubuntu-latest services: postgres: @@ -27,7 +30,7 @@ jobs: ports: - 3306:3306 - name: Go unit tests + name: Go unit tests (ubuntu-latest) steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -53,10 +56,41 @@ jobs: env: SHIORI_TEST_PG_URL: "postgres://shiori:shiori@localhost:5432/shiori?sslmode=disable" SHIORI_TEST_MYSQL_URL: "shiori:shiori@(localhost:3306)/shiori" + CGO_ENABLED: 1 # go test -race requires cgo - - run: CGO_ENABLED=0 go build -tags osusergo,netgo -ldflags="-s -w -X main.version=$(git describe --tags) -X main.date=$(date --iso-8601=seconds)" + - run: go build -tags osusergo,netgo -ldflags="-s -w -X main.version=$(git describe --tags) -X main.date=$(date --iso-8601=seconds)" - name: Upload coverage reports to Codecov uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # 3.1.5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + test-windows-macos: + strategy: + matrix: + os: [windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: Go unit tests (${{ matrix.os }}) + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup go + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version-file: ./go.mod + + - uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # 3.3.3 + with: + path: | + ~/.cache/go-build + ~/go/pkg + key: golangci-lint.cache-{platform-arch}-{interval_number}-{go.mod_hash} + restore-keys: | + golangci-lint.cache-{interval_number}- + golangci-lint.cache- + + - run: make unittest GO_TEST_FLAGS="-tags test_sqlite_only" + env: + CGO_ENABLED: 1 # go test -race requires cgo + + - run: go build -tags osusergo,netgo -ldflags="-s -w -X main.version=$(git describe --tags) -X main.date=$(date --iso-8601=seconds)" diff --git a/Makefile b/Makefile index 0a4c92402..59a2044de 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ BASH ?= $(shell command -v bash 2> /dev/null) SHIORI_DIR ?= dev-data # Testing -GO_TEST_FLAGS ?= -v -race -count=1 -covermode=atomic -coverprofile=coverage.out +override GO_TEST_FLAGS += -v -race -count=1 -covermode=atomic -coverprofile=coverage.out GOTESTFMT_FLAGS ?= # Build diff --git a/internal/config/config_test.go b/internal/config/config_test.go index dc31ad78d..36f22717a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -67,12 +67,7 @@ func TestReadDotEnv(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { tmpDir, err := os.MkdirTemp("", "") require.NoError(t, err) - - os.Chdir(tmpDir) - - t.Cleanup(func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }) + require.NoError(t, os.Chdir(tmpDir)) // Write the .env file in the temporary directory handler, err := os.OpenFile(".env", os.O_CREATE|os.O_WRONLY, 0655) @@ -89,12 +84,7 @@ func TestReadDotEnv(t *testing.T) { t.Run("no file", func(t *testing.T) { tmpDir, err := os.MkdirTemp("", "") require.NoError(t, err) - - os.Chdir(tmpDir) - - t.Cleanup(func() { - require.NoError(t, os.RemoveAll(tmpDir)) - }) + require.NoError(t, os.Chdir(tmpDir)) e := readDotEnv(log) diff --git a/internal/core/ebook_test.go b/internal/core/ebook_test.go index e2f0ce492..67b06d218 100644 --- a/internal/core/ebook_test.go +++ b/internal/core/ebook_test.go @@ -21,11 +21,10 @@ func TestGenerateEbook(t *testing.T) { t.Run("Successful ebook generate", func(t *testing.T) { t.Run("valid bookmarkId that return HasEbook true", func(t *testing.T) { - // test cae - tempDir := t.TempDir() - dstDir := t.TempDir() + dstFile := "/ebook/1.epub" + tmpDir := t.TempDir() - deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), dstDir)) + deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), tmpDir)) mockRequest := core.ProcessRequest{ Bookmark: model.BookmarkDTO{ @@ -34,19 +33,19 @@ func TestGenerateEbook(t *testing.T) { HTML: "Example HTML", HasEbook: false, }, - DataDir: dstDir, + DataDir: tmpDir, ContentType: "text/html", } - - bookmark, err := core.GenerateEbook(deps, mockRequest, fp.Join(tempDir, "1")) + bookmark, err := core.GenerateEbook(deps, mockRequest, dstFile) assert.True(t, bookmark.HasEbook) assert.NoError(t, err) }) t.Run("ebook generate with valid BookmarkID EbookExist ImagePathExist ReturnWithHasEbookTrue", func(t *testing.T) { - dstDir := t.TempDir() + dstFile := "/ebook/2.epub" + tmpDir := t.TempDir() - deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), dstDir)) + deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), tmpDir)) bookmark := model.BookmarkDTO{ ID: 2, @@ -54,29 +53,30 @@ func TestGenerateEbook(t *testing.T) { } mockRequest := core.ProcessRequest{ Bookmark: bookmark, - DataDir: dstDir, + DataDir: tmpDir, ContentType: "text/html", } // Create the thumbnail file imagePath := model.GetThumbnailPath(&bookmark) - deps.Domains.Storage.FS().MkdirAll(fp.Dir(imagePath), os.ModePerm) + imagedirPath := fp.Dir(imagePath) + deps.Domains.Storage.FS().MkdirAll(imagedirPath, os.ModePerm) file, err := deps.Domains.Storage.FS().Create(imagePath) if err != nil { t.Fatal(err) } defer file.Close() - bookmark, err = core.GenerateEbook(deps, mockRequest, model.GetEbookPath(&bookmark)) - expectedImagePath := "/bookmark/2/thumb" + bookmark, err = core.GenerateEbook(deps, mockRequest, dstFile) + expectedImagePath := string(fp.Separator) + fp.Join("bookmark", "2", "thumb") assert.NoError(t, err) assert.True(t, bookmark.HasEbook) assert.Equalf(t, expectedImagePath, bookmark.ImageURL, "Expected imageURL %s, but got %s", expectedImagePath, bookmark.ImageURL) }) t.Run("generate ebook valid BookmarkID EbookExist ReturnHasArchiveTrue", func(t *testing.T) { - tempDir := t.TempDir() - dstDir := t.TempDir() + dstFile := "/ebook/3.epub" + tmpDir := t.TempDir() - deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), dstDir)) + deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), tmpDir)) bookmark := model.BookmarkDTO{ ID: 3, @@ -84,47 +84,50 @@ func TestGenerateEbook(t *testing.T) { } mockRequest := core.ProcessRequest{ Bookmark: bookmark, - DataDir: dstDir, + DataDir: tmpDir, ContentType: "text/html", } // Create the archive file archivePath := model.GetArchivePath(&bookmark) - deps.Domains.Storage.FS().MkdirAll(fp.Dir(archivePath), os.ModePerm) + archiveDirPath := fp.Dir(archivePath) + deps.Domains.Storage.FS().MkdirAll(archiveDirPath, os.ModePerm) file, err := deps.Domains.Storage.FS().Create(archivePath) if err != nil { t.Fatal(err) } defer file.Close() - bookmark, err = core.GenerateEbook(deps, mockRequest, fp.Join(tempDir, "1")) + bookmark, err = core.GenerateEbook(deps, mockRequest, fp.Join(dstFile, "1")) assert.True(t, bookmark.HasArchive) assert.NoError(t, err) }) }) t.Run("specific ebook generate case", func(t *testing.T) { t.Run("invalid bookmarkId that return Error", func(t *testing.T) { - tempDir := t.TempDir() + dstFile := "/ebook/0.epub" + tmpDir := t.TempDir() mockRequest := core.ProcessRequest{ Bookmark: model.BookmarkDTO{ ID: 0, HasEbook: false, }, - DataDir: tempDir, + DataDir: tmpDir, ContentType: "text/html", } - bookmark, err := core.GenerateEbook(deps, mockRequest, tempDir) + bookmark, err := core.GenerateEbook(deps, mockRequest, dstFile) assert.Equal(t, model.BookmarkDTO{ ID: 0, HasEbook: false, }, bookmark) - assert.Error(t, err) + assert.EqualError(t, err, "bookmark ID is not valid") }) t.Run("ebook exist return HasEbook true", func(t *testing.T) { - dstDir := t.TempDir() + dstFile := "/ebook/1.epub" + tmpDir := t.TempDir() - deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), dstDir)) + deps.Domains.Storage = domains.NewStorageDomain(deps, afero.NewBasePathFs(afero.NewOsFs(), tmpDir)) bookmark := model.BookmarkDTO{ ID: 1, @@ -132,40 +135,42 @@ func TestGenerateEbook(t *testing.T) { } mockRequest := core.ProcessRequest{ Bookmark: bookmark, - DataDir: dstDir, + DataDir: tmpDir, ContentType: "text/html", } // Create the ebook file - ebookFilePath := model.GetEbookPath(&bookmark) - deps.Domains.Storage.FS().MkdirAll(fp.Dir(ebookFilePath), os.ModePerm) - file, err := deps.Domains.Storage.FS().Create(ebookFilePath) + ebookPath := model.GetEbookPath(&bookmark) + ebookDirPath := fp.Dir(ebookPath) + deps.Domains.Storage.FS().MkdirAll(ebookDirPath, os.ModePerm) + file, err := deps.Domains.Storage.FS().Create(ebookPath) if err != nil { t.Fatal(err) } defer file.Close() - bookmark, err = core.GenerateEbook(deps, mockRequest, ebookFilePath) + bookmark, err = core.GenerateEbook(deps, mockRequest, dstFile) assert.True(t, bookmark.HasEbook) assert.NoError(t, err) }) t.Run("generate ebook valid BookmarkID RetuenError for PDF file", func(t *testing.T) { - tempDir := t.TempDir() + dstFile := "/ebook/1.epub" + tmpDir := t.TempDir() mockRequest := core.ProcessRequest{ Bookmark: model.BookmarkDTO{ ID: 1, HasEbook: false, }, - DataDir: tempDir, + DataDir: tmpDir, ContentType: "application/pdf", } - bookmark, err := core.GenerateEbook(deps, mockRequest, tempDir) + bookmark, err := core.GenerateEbook(deps, mockRequest, dstFile) assert.False(t, bookmark.HasEbook) assert.Error(t, err) - assert.Contains(t, err.Error(), "can't create ebook for pdf") + assert.EqualError(t, err, "can't create ebook for pdf") }) }) } diff --git a/internal/core/processing_test.go b/internal/core/processing_test.go index 0e24f6007..af3699abf 100644 --- a/internal/core/processing_test.go +++ b/internal/core/processing_test.go @@ -3,8 +3,6 @@ package core_test import ( "bytes" "context" - "net/http" - "net/http/httptest" "os" fp "path/filepath" "testing" @@ -14,6 +12,7 @@ import ( "github.com/go-shiori/shiori/internal/testutil" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDownloadBookImage(t *testing.T) { @@ -24,51 +23,47 @@ func TestDownloadBookImage(t *testing.T) { t.Run("fails", func(t *testing.T) { // images is too small with unsupported format with a valid URL imageURL := "https://github.com/go-shiori/shiori/blob/master/internal/view/assets/res/apple-touch-icon-152x152.png" - tempDir := t.TempDir() - dstPath := fp.Join(tempDir, "1") - defer os.Remove(dstPath) + tmpDir, err := os.MkdirTemp("", "") + require.NoError(t, err) + dstFile := fp.Join(tmpDir, "image.png") // Act - err := core.DownloadBookImage(deps, imageURL, dstPath) + err = core.DownloadBookImage(deps, imageURL, dstFile) // Assert assert.EqualError(t, err, "unsupported image type") - assert.False(t, deps.Domains.Storage.FileExists(dstPath)) + assert.False(t, deps.Domains.Storage.FileExists(dstFile)) }) t.Run("successful download image", func(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "") + require.NoError(t, err) + require.NoError(t, os.Chdir(tmpDir)) // Arrange imageURL := "https://raw.githubusercontent.com/go-shiori/shiori/master/docs/readme/cover.png" - tempDir := t.TempDir() - dstPath := fp.Join(tempDir, "1") - defer os.Remove(dstPath) + dstFile := "." + string(fp.Separator) + "cover.png" // Act - err := core.DownloadBookImage(deps, imageURL, dstPath) + err = core.DownloadBookImage(deps, imageURL, dstFile) // Assert assert.NoError(t, err) - assert.True(t, deps.Domains.Storage.FileExists(dstPath)) + assert.True(t, deps.Domains.Storage.FileExists(dstFile)) }) t.Run("successful download medium size image", func(t *testing.T) { - // create a file server handler for the 'testdata' directory - fs := http.FileServer(http.Dir("../../testdata/")) - - // start a test server with the file server handler - server := httptest.NewServer(fs) - defer server.Close() + tmpDir, err := os.MkdirTemp("", "") + require.NoError(t, err) + require.NoError(t, os.Chdir(tmpDir)) // Arrange - imageURL := server.URL + "/medium_image.png" - tempDir := t.TempDir() - dstPath := fp.Join(tempDir, "1") - defer os.Remove(dstPath) + imageURL := "https://raw.githubusercontent.com/go-shiori/shiori/master/testdata/medium_image.png" + dstFile := "." + string(fp.Separator) + "medium_image.png" // Act - err := core.DownloadBookImage(deps, imageURL, dstPath) + err = core.DownloadBookImage(deps, imageURL, dstFile) // Assert assert.NoError(t, err) - assert.True(t, deps.Domains.Storage.FileExists(dstPath)) + assert.True(t, deps.Domains.Storage.FileExists(dstFile)) }) }) } @@ -78,6 +73,7 @@ func TestProcessBookmark(t *testing.T) { _, deps := testutil.GetTestConfigurationAndDependencies(t, context.TODO(), logger) t.Run("ProcessRequest with sucssesful result", func(t *testing.T) { + tmpDir := t.TempDir() t.Run("Normal without image", func(t *testing.T) { bookmark := model.BookmarkDTO{ ID: 1, @@ -92,7 +88,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } @@ -112,7 +108,7 @@ func TestProcessBookmark(t *testing.T) { } }) t.Run("Normal with multipleimage", func(t *testing.T) { - + tmpDir := t.TempDir() html := `html @@ -136,7 +132,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } @@ -156,18 +152,12 @@ func TestProcessBookmark(t *testing.T) { } }) t.Run("ProcessRequest sucssesful with multipleimage included favicon and Thumbnail ", func(t *testing.T) { - // create a file server handler for the 'testdata' directory - fs := http.FileServer(http.Dir("../../testdata/")) - - // start a test server with the file server handler - server := httptest.NewServer(fs) - defer server.Close() - + tmpDir := t.TempDir() html := `html - - + +

This is an example article

@@ -186,12 +176,12 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } expected, _, _ := core.ProcessBookmark(deps, request) - + assert.True(t, deps.Domains.Storage.FileExists(fp.Join("thumb", "1"))) if expected.ID != bookmark.ID { t.Errorf("Unexpected ID: got %v, want %v", expected.ID, bookmark.ID) } @@ -206,6 +196,7 @@ func TestProcessBookmark(t *testing.T) { } }) t.Run("ProcessRequest sucssesful with empty title ", func(t *testing.T) { + tmpDir := t.TempDir() bookmark := model.BookmarkDTO{ ID: 1, URL: "https://example.com", @@ -219,7 +210,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } @@ -239,6 +230,7 @@ func TestProcessBookmark(t *testing.T) { } }) t.Run("ProcessRequest sucssesful with empty Excerpt", func(t *testing.T) { + tmpDir := t.TempDir() bookmark := model.BookmarkDTO{ ID: 1, URL: "https://example.com", @@ -252,7 +244,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: false, } @@ -272,6 +264,7 @@ func TestProcessBookmark(t *testing.T) { } }) t.Run("Specific case", func(t *testing.T) { + tmpDir := t.TempDir() t.Run("ProcessRequest with ID zero", func(t *testing.T) { bookmark := model.BookmarkDTO{ @@ -287,7 +280,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "text/html", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } @@ -298,7 +291,7 @@ func TestProcessBookmark(t *testing.T) { }) t.Run("ProcessRequest that content type not zero", func(t *testing.T) { - + tmpDir := t.TempDir() bookmark := model.BookmarkDTO{ ID: 1, URL: "https://example.com", @@ -312,7 +305,7 @@ func TestProcessBookmark(t *testing.T) { Bookmark: bookmark, Content: content, ContentType: "application/pdf", - DataDir: "/tmp", + DataDir: tmpDir, KeepTitle: true, KeepExcerpt: true, } diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 01f8a9d91..cb00b5402 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -6,10 +6,11 @@ import ( "github.com/go-shiori/shiori/internal/model" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type databaseTestCase func(t *testing.T, db DB) -type testDatabaseFactory func(ctx context.Context) (DB, error) +type testDatabaseFactory func(t *testing.T, ctx context.Context) (DB, error) func testDatabase(t *testing.T, dbFactory testDatabaseFactory) { tests := map[string]databaseTestCase{ @@ -30,12 +31,17 @@ func testDatabase(t *testing.T, dbFactory testDatabaseFactory) { // Tags "testCreateTag": testCreateTag, "testCreateTags": testCreateTags, + // Accounts + "testSaveAccount": testSaveAccount, + "testSaveAccountSetting": testSaveAccountSettings, + "testGetAccount": testGetAccount, + "testGetAccounts": testGetAccounts, } for testName, testCase := range tests { t.Run(testName, func(tInner *testing.T) { ctx := context.TODO() - db, err := dbFactory(ctx) + db, err := dbFactory(t, ctx) assert.NoError(tInner, err, "Error recreating database") testCase(tInner, db) }) @@ -323,3 +329,98 @@ func testCreateTags(t *testing.T, db DB) { err := db.CreateTags(ctx, model.Tag{Name: "shiori"}, model.Tag{Name: "shiori2"}) assert.NoError(t, err, "Save tag must not fail") } + +func testSaveAccount(t *testing.T, db DB) { + ctx := context.TODO() + + t.Run("success", func(t *testing.T) { + acc := model.Account{ + Username: "testuser", + Config: model.UserConfig{}, + } + + err := db.SaveAccount(ctx, acc) + require.Nil(t, err) + }) +} + +func testSaveAccountSettings(t *testing.T, db DB) { + ctx := context.TODO() + + t.Run("success", func(t *testing.T) { + acc := model.Account{ + Username: "test", + Config: model.UserConfig{}, + } + + err := db.SaveAccountSettings(ctx, acc) + require.Nil(t, err) + }) +} + +func testGetAccount(t *testing.T, db DB) { + ctx := context.TODO() + + t.Run("success", func(t *testing.T) { + // Insert test accounts + testAccounts := []model.Account{ + {Username: "foo", Password: "bar", Owner: false}, + {Username: "hello", Password: "world", Owner: false}, + {Username: "foo_bar", Password: "foobar", Owner: true}, + } + for _, acc := range testAccounts { + err := db.SaveAccount(ctx, acc) + assert.Nil(t, err) + + // Successful case + account, exists, err := db.GetAccount(ctx, acc.Username) + assert.Nil(t, err) + assert.True(t, exists, "Expected account to exist") + assert.Equal(t, acc.Username, account.Username) + } + // Falid case + account, exists, err := db.GetAccount(ctx, "foobar") + assert.NotNil(t, err) + assert.False(t, exists, "Expected account to exist") + assert.Empty(t, account.Username) + }) +} + +func testGetAccounts(t *testing.T, db DB) { + ctx := context.TODO() + + t.Run("success", func(t *testing.T) { + // Insert test accounts + testAccounts := []model.Account{ + {Username: "foo", Password: "bar", Owner: false}, + {Username: "hello", Password: "world", Owner: false}, + {Username: "foo_bar", Password: "foobar", Owner: true}, + } + for _, acc := range testAccounts { + err := db.SaveAccount(ctx, acc) + assert.Nil(t, err) + } + + // Successful case + // without opt + accounts, err := db.GetAccounts(ctx, GetAccountsOptions{}) + assert.NoError(t, err) + assert.Equal(t, 3, len(accounts)) + // with owner + accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Owner: true}) + assert.NoError(t, err) + assert.Equal(t, 1, len(accounts)) + // with opt + accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "foo"}) + assert.NoError(t, err) + assert.Equal(t, 2, len(accounts)) + // with opt and owner + accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "hello", Owner: false}) + assert.NoError(t, err) + assert.Equal(t, 1, len(accounts)) + // with not result + accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "shiori"}) + assert.NoError(t, err) + assert.Equal(t, 0, len(accounts)) + }) +} diff --git a/internal/database/mysql_test.go b/internal/database/mysql_test.go index e591464c0..9ff2f2fe5 100644 --- a/internal/database/mysql_test.go +++ b/internal/database/mysql_test.go @@ -1,17 +1,17 @@ +//go:build !test_sqlite_only +// +build !test_sqlite_only + package database import ( "context" "log" "os" - "path/filepath" "testing" - "github.com/go-shiori/shiori/internal/model" "github.com/golang-migrate/migrate/v4" "github.com/jmoiron/sqlx" "github.com/pkg/errors" - "github.com/stretchr/testify/assert" ) func init() { @@ -21,7 +21,7 @@ func init() { } } -func mysqlTestDatabaseFactory(ctx context.Context) (DB, error) { +func mysqlTestDatabaseFactory(_ *testing.T, ctx context.Context) (DB, error) { connString := os.Getenv("SHIORI_TEST_MYSQL_URL") db, err := OpenMySQLDatabase(ctx, connString) if err != nil { @@ -61,117 +61,3 @@ func mysqlTestDatabaseFactory(ctx context.Context) (DB, error) { func TestMysqlsDatabase(t *testing.T) { testDatabase(t, mysqlTestDatabaseFactory) } - -func TestSaveAccountSettingsMySql(t *testing.T) { - ctx := context.TODO() - - db, err := mysqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Mock data - account := model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - - // Successful case - err = db.SaveAccountSettings(ctx, account) - assert.NoError(t, err) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - account = model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - err = db.SaveAccountSettings(ctx, account) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} - -func TestGetAccountsMySql(t *testing.T) { - ctx := context.TODO() - - db, err := mysqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Insert test accounts - testAccounts := []model.Account{ - {Username: "foo", Password: "bar", Owner: false}, - {Username: "hello", Password: "world", Owner: false}, - {Username: "foo_bar", Password: "foobar", Owner: true}, - } - for _, acc := range testAccounts { - err := db.SaveAccount(ctx, acc) - assert.Nil(t, err) - } - - // Successful case - // without opt - accounts, err := db.GetAccounts(ctx, GetAccountsOptions{}) - assert.NoError(t, err) - assert.Equal(t, 3, len(accounts)) - // with owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Owner: true}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with opt - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "foo"}) - assert.NoError(t, err) - assert.Equal(t, 2, len(accounts)) - // with opt and owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "hello", Owner: false}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with not result - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "shiori"}) - assert.NoError(t, err) - assert.Equal(t, 0, len(accounts)) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - // with invalid query - opts := GetAccountsOptions{Keyword: "foo", Owner: true} - _, err = db.GetAccounts(ctx, opts) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} - -func TestGetAccountMySql(t *testing.T) { - ctx := context.TODO() - - db, err := mysqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Insert test accounts - testAccounts := []model.Account{ - {Username: "foo", Password: "bar", Owner: false}, - {Username: "hello", Password: "world", Owner: false}, - {Username: "foo_bar", Password: "foobar", Owner: true}, - } - for _, acc := range testAccounts { - err := db.SaveAccount(ctx, acc) - assert.Nil(t, err) - - // Successful case - account, exists, err := db.GetAccount(ctx, acc.Username) - assert.Nil(t, err) - assert.True(t, exists, "Expected account to exist") - assert.Equal(t, acc.Username, account.Username) - } - // Falid case - account, exists, err := db.GetAccount(ctx, "foobar") - assert.NotNil(t, err) - assert.False(t, exists, "Expected account to exist") - assert.Empty(t, account.Username) -} diff --git a/internal/database/pg_test.go b/internal/database/pg_test.go index cabfe89b6..eb1620386 100644 --- a/internal/database/pg_test.go +++ b/internal/database/pg_test.go @@ -1,3 +1,6 @@ +//go:build !test_sqlite_only +// +build !test_sqlite_only + package database import ( @@ -5,12 +8,9 @@ import ( "errors" "log" "os" - "path/filepath" "testing" - "github.com/go-shiori/shiori/internal/model" "github.com/golang-migrate/migrate/v4" - "github.com/stretchr/testify/assert" ) func init() { @@ -20,7 +20,7 @@ func init() { } } -func postgresqlTestDatabaseFactory(ctx context.Context) (DB, error) { +func postgresqlTestDatabaseFactory(_ *testing.T, ctx context.Context) (DB, error) { db, err := OpenPGDatabase(ctx, os.Getenv("SHIORI_TEST_PG_URL")) if err != nil { return nil, err @@ -41,116 +41,3 @@ func postgresqlTestDatabaseFactory(ctx context.Context) (DB, error) { func TestPostgresDatabase(t *testing.T) { testDatabase(t, postgresqlTestDatabaseFactory) } - -func TestSaveAccountSettingsPg(t *testing.T) { - ctx := context.TODO() - - db, err := postgresqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Mock data - account := model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - - // Successful case - err = db.SaveAccountSettings(ctx, account) - assert.NoError(t, err) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - account = model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - err = db.SaveAccountSettings(ctx, account) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} -func TestGetAccountsPg(t *testing.T) { - ctx := context.TODO() - - db, err := postgresqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Insert test accounts - testAccounts := []model.Account{ - {Username: "foo", Password: "bar", Owner: false}, - {Username: "hello", Password: "world", Owner: false}, - {Username: "foo_bar", Password: "foobar", Owner: true}, - } - for _, acc := range testAccounts { - err := db.SaveAccount(ctx, acc) - assert.Nil(t, err) - } - - // Successful case - // without opt - accounts, err := db.GetAccounts(ctx, GetAccountsOptions{}) - assert.NoError(t, err) - assert.Equal(t, 3, len(accounts)) - // with owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Owner: true}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with opt - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "foo"}) - assert.NoError(t, err) - assert.Equal(t, 2, len(accounts)) - // with opt and owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "hello", Owner: false}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with not result - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "shiori"}) - assert.NoError(t, err) - assert.Equal(t, 0, len(accounts)) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - // with invalid query - opts := GetAccountsOptions{Keyword: "foo", Owner: true} - _, err = db.GetAccounts(ctx, opts) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} - -func TestGetAccountPg(t *testing.T) { - ctx := context.TODO() - - db, err := postgresqlTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Insert test accounts - testAccounts := []model.Account{ - {Username: "foo", Password: "bar", Owner: false}, - {Username: "hello", Password: "world", Owner: false}, - {Username: "foo_bar", Password: "foobar", Owner: true}, - } - for _, acc := range testAccounts { - err := db.SaveAccount(ctx, acc) - assert.Nil(t, err) - - // Successful case - account, exists, err := db.GetAccount(ctx, acc.Username) - assert.Nil(t, err) - assert.True(t, exists, "Expected account to exist") - assert.Equal(t, acc.Username, account.Username) - } - // Falid case - account, exists, err := db.GetAccount(ctx, "foobar") - assert.NotNil(t, err) - assert.False(t, exists, "Expected account to exist") - assert.Empty(t, account.Username) -} diff --git a/internal/database/sqlite_test.go b/internal/database/sqlite_test.go index 509df0e5c..264a05e44 100644 --- a/internal/database/sqlite_test.go +++ b/internal/database/sqlite_test.go @@ -10,18 +10,14 @@ import ( "github.com/golang-migrate/migrate/v4" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var sqliteDatabaseTestPath string +func sqliteTestDatabaseFactory(t *testing.T, ctx context.Context) (DB, error) { + tmpDir, err := os.MkdirTemp("", "") + require.NoError(t, err) -func init() { - sqliteDatabaseTestPath = filepath.Join(os.TempDir(), "shiori.db") -} - -func sqliteTestDatabaseFactory(ctx context.Context) (DB, error) { - os.Remove(sqliteDatabaseTestPath) - - db, err := OpenSQLiteDatabase(ctx, sqliteDatabaseTestPath) + db, err := OpenSQLiteDatabase(ctx, filepath.Join(tmpDir, "shiori.db")) if err != nil { return nil, err } @@ -47,7 +43,7 @@ func TestSqliteDatabase(t *testing.T) { func testSqliteGetBookmarksWithDash(t *testing.T) { ctx := context.TODO() - db, err := sqliteTestDatabaseFactory(ctx) + db, err := sqliteTestDatabaseFactory(t, ctx) assert.NoError(t, err) book := model.BookmarkDTO{ @@ -76,105 +72,3 @@ func testSqliteGetBookmarksWithDash(t *testing.T) { assert.Equal(t, savedBookmark.ID, results[0].ID, "bookmark should be the one saved") } -func TestSQLiteDatabase_SaveAccount(t *testing.T) { - - ctx := context.TODO() - - // Initialize not correct database - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err := factory(ctx) - assert.Nil(t, err) - - // Test falid database - acc := model.Account{} - err = db.SaveAccount(ctx, acc) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") - -} - -func TestSaveAccountSettings(t *testing.T) { - ctx := context.TODO() - - db, err := sqliteTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Mock data - account := model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - - // Successful case - err = db.SaveAccountSettings(ctx, account) - assert.NoError(t, err) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - account = model.Account{ - Username: "testuser", - Config: model.UserConfig{}, - } - err = db.SaveAccountSettings(ctx, account) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} - -func TestGetAccounts(t *testing.T) { - ctx := context.TODO() - - db, err := sqliteTestDatabaseFactory(ctx) - assert.NoError(t, err) - - // Insert test accounts - testAccounts := []model.Account{ - {Username: "foo", Password: "bar", Owner: false}, - {Username: "hello", Password: "world", Owner: false}, - {Username: "foo_bar", Password: "foobar", Owner: true}, - } - for _, acc := range testAccounts { - err := db.SaveAccount(ctx, acc) - assert.Nil(t, err) - } - - // Successful case - // without opt - accounts, err := db.GetAccounts(ctx, GetAccountsOptions{}) - assert.NoError(t, err) - assert.Equal(t, 3, len(accounts)) - // with owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Owner: true}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with opt - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "foo"}) - assert.NoError(t, err) - assert.Equal(t, 2, len(accounts)) - // with opt and owner - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "hello", Owner: false}) - assert.NoError(t, err) - assert.Equal(t, 1, len(accounts)) - // with not result - accounts, err = db.GetAccounts(ctx, GetAccountsOptions{Keyword: "shiori"}) - assert.NoError(t, err) - assert.Equal(t, 0, len(accounts)) - - // Initialize not correct database - ctx = context.TODO() - factory := func(ctx context.Context) (DB, error) { - return OpenSQLiteDatabase(ctx, filepath.Join(os.TempDir(), "shiori_test.db")) - } - db, err = factory(ctx) - assert.Nil(t, err) - // with invalid query - opts := GetAccountsOptions{Keyword: "foo", Owner: true} - _, err = db.GetAccounts(ctx, opts) - assert.NotNil(t, err) - assert.Contains(t, err.Error(), "SQL logic error: no such table: account (1)") -} diff --git a/internal/http/routes/frontend.go b/internal/http/routes/frontend.go index 7504dc871..364a5d2a4 100644 --- a/internal/http/routes/frontend.go +++ b/internal/http/routes/frontend.go @@ -3,7 +3,7 @@ package routes import ( "embed" "net/http" - "path/filepath" + "path" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" @@ -27,7 +27,7 @@ func (fs assetsFS) Exists(prefix string, path string) bool { } func (fs assetsFS) Open(name string) (http.File, error) { - f, err := fs.FileSystem.Open(filepath.Join("assets", name)) + f, err := fs.FileSystem.Open(path.Join("assets", name)) if err != nil { logrus.WithError(err).WithField("path", name).Error("requested frontend file not found") } diff --git a/internal/testutil/shiori.go b/internal/testutil/shiori.go index 86120658f..fd6272cba 100644 --- a/internal/testutil/shiori.go +++ b/internal/testutil/shiori.go @@ -21,18 +21,21 @@ func GetTestConfigurationAndDependencies(t *testing.T, ctx context.Context, logg tmp, err := os.CreateTemp("", "") require.NoError(t, err) + t.Cleanup(func() { + os.Remove(tmp.Name()) + }) cfg := config.ParseServerConfiguration(ctx, logger) cfg.Http.SecretKey = []byte("test") - tempDir, err := os.MkdirTemp("", "") + tmpDir, err := os.MkdirTemp("", "") require.NoError(t, err) db, err := database.OpenSQLiteDatabase(ctx, tmp.Name()) require.NoError(t, err) require.NoError(t, db.Migrate()) - cfg.Storage.DataDir = tempDir + cfg.Storage.DataDir = tmpDir deps := dependencies.NewDependencies(logger, db, cfg) deps.Database = db diff --git a/scripts/test.sh b/scripts/test.sh index 7d2b8bca4..1893616da 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,13 +1,12 @@ #!/bin/bash # Check if gotestfmt is installed -GOTESTFMT=$(which gotestfmt) -if [[ -z "${GOTESTFMT}" ]]; then +if ! [ -x "$(command -v gotestfmt)" ]; then echo "gotestfmt not found. Using test standard output." fi # if gotestfmt is installed, run with it -if [[ -n "${GOTESTFMT}" ]]; then +if [ -x "$(command -v gotestfmt)" ]; then set -o pipefail go test ./... ${GO_TEST_FLAGS} -json | gotestfmt ${GOTESTFMT_FLAGS} else