diff options
Diffstat (limited to 'tests/integration/db_collation_test.go')
-rw-r--r-- | tests/integration/db_collation_test.go | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go new file mode 100644 index 00000000..0e5bf00e --- /dev/null +++ b/tests/integration/db_collation_test.go @@ -0,0 +1,149 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +type TestCollationTbl struct { + ID int64 + Txt string `xorm:"VARCHAR(10) UNIQUE"` +} + +func TestDatabaseCollationSelfCheckUI(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + assertSelfCheckExists := func(exists bool) { + expectedHTTPResponse := http.StatusOK + if !exists { + expectedHTTPResponse = http.StatusNotFound + } + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/self_check") + resp := session.MakeRequest(t, req, expectedHTTPResponse) + htmlDoc := NewHTMLParser(t, resp.Body) + + htmlDoc.AssertElement(t, "a.item[href*='/admin/self_check']", exists) + } + + if setting.Database.Type.IsMySQL() { + assertSelfCheckExists(true) + } else { + assertSelfCheckExists(false) + } +} + +func TestDatabaseCollation(t *testing.T) { + x := db.GetEngine(db.DefaultContext).(*xorm.Engine) + + // all created tables should use case-sensitive collation by default + _, _ = x.Exec("DROP TABLE IF EXISTS test_collation_tbl") + err := x.Sync(&TestCollationTbl{}) + require.NoError(t, err) + _, _ = x.Exec("INSERT INTO test_collation_tbl (txt) VALUES ('main')") + _, _ = x.Exec("INSERT INTO test_collation_tbl (txt) VALUES ('Main')") // case-sensitive, so it inserts a new row + _, _ = x.Exec("INSERT INTO test_collation_tbl (txt) VALUES ('main')") // duplicate, so it doesn't insert + cnt, err := x.Count(&TestCollationTbl{}) + require.NoError(t, err) + assert.EqualValues(t, 2, cnt) + _, _ = x.Exec("DROP TABLE IF EXISTS test_collation_tbl") + + // by default, SQLite3 and PostgreSQL are using case-sensitive collations, but MySQL is not. + if !setting.Database.Type.IsMySQL() { + t.Skip("only MySQL requires the case-sensitive collation check at the moment") + return + } + + t.Run("Default startup makes database collation case-sensitive", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + r, err := db.CheckCollations(x) + require.NoError(t, err) + assert.True(t, r.IsCollationCaseSensitive(r.DatabaseCollation)) + assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) + assert.NotEmpty(t, r.AvailableCollation) + assert.Empty(t, r.InconsistentCollationColumns) + + // and by the way test the helper functions + if setting.Database.Type.IsMySQL() { + assert.True(t, r.IsCollationCaseSensitive("utf8mb4_bin")) + assert.True(t, r.IsCollationCaseSensitive("utf8mb4_xxx_as_cs")) + assert.False(t, r.IsCollationCaseSensitive("utf8mb4_general_ci")) + assert.True(t, r.CollationEquals("abc", "abc")) + assert.True(t, r.CollationEquals("abc", "utf8mb4_abc")) + assert.False(t, r.CollationEquals("utf8mb4_general_ci", "utf8mb4_unicode_ci")) + } else { + assert.Fail(t, "unexpected database type") + } + }) + + t.Run("Convert tables to utf8mb4_bin", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_bin")() + require.NoError(t, db.ConvertDatabaseTable()) + time.Sleep(5 * time.Second) + + r, err := db.CheckCollations(x) + require.NoError(t, err) + assert.Equal(t, "utf8mb4_bin", r.DatabaseCollation) + assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) + assert.Empty(t, r.InconsistentCollationColumns) + + _, _ = x.Exec("DROP TABLE IF EXISTS test_tbl") + _, err = x.Exec("CREATE TABLE test_tbl (txt varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL)") + require.NoError(t, err) + r, err = db.CheckCollations(x) + require.NoError(t, err) + assert.Contains(t, r.InconsistentCollationColumns, "test_tbl.txt") + }) + + t.Run("Convert tables to utf8mb4_general_ci", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_general_ci")() + require.NoError(t, db.ConvertDatabaseTable()) + time.Sleep(5 * time.Second) + + r, err := db.CheckCollations(x) + require.NoError(t, err) + assert.Equal(t, "utf8mb4_general_ci", r.DatabaseCollation) + assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) + assert.Empty(t, r.InconsistentCollationColumns) + + _, _ = x.Exec("DROP TABLE IF EXISTS test_tbl") + _, err = x.Exec("CREATE TABLE test_tbl (txt varchar(10) COLLATE utf8mb4_bin NOT NULL)") + require.NoError(t, err) + r, err = db.CheckCollations(x) + require.NoError(t, err) + assert.Contains(t, r.InconsistentCollationColumns, "test_tbl.txt") + }) + + t.Run("Convert tables to default case-sensitive collation", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + defer test.MockVariableValue(&setting.Database.CharsetCollation, "")() + require.NoError(t, db.ConvertDatabaseTable()) + time.Sleep(5 * time.Second) + + r, err := db.CheckCollations(x) + require.NoError(t, err) + assert.True(t, r.IsCollationCaseSensitive(r.DatabaseCollation)) + assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) + assert.Empty(t, r.InconsistentCollationColumns) + }) +} |