Repository: openimsdk/open-im-server
Pull request #1274 (2023-10-24 09:02:23)

chore(deps): bump the github-actions group with 1 update
Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).

<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-node/releases">actions/setup-node's releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<h2>What's Changed</h2>
<p>In scope of this release we changed version of node runtime for action from node16 to node20 and updated dependencies in <a href="https://redirect.github.com/actions/setup-node/pull/866">actions/setup-node#866</a></p>
<p>Besides, release contains such changes as:</p>
<ul>
<li>Upgrade actions/checkout to v4 by <a href="https://github.com/gmembre-zenika"><code>@​gmembre-zenika</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/868">actions/setup-node#868</a></li>
<li>Update actions/checkout for documentation and yaml by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/876">actions/setup-node#876</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gmembre-zenika"><code>@​gmembre-zenika</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-node/pull/868">actions/setup-node#868</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-node/compare/v3...v4.0.0">https://github.com/actions/setup-node/compare/v3...v4.0.0</a></p>
<h2>v3.8.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Update semver by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/861">actions/setup-node#861</a></li>
<li>Update temp directory creation by <a href="https://github.com/nikolai-laevskii"><code>@​nikolai-laevskii</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/859">actions/setup-node#859</a></li>
<li>Bump <code>@​babel/traverse</code> from 7.15.4 to 7.23.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/870">actions/setup-node#870</a></li>
<li>Add notice about binaries not being updated yet by <a href="https://github.com/nikolai-laevskii"><code>@​nikolai-laevskii</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/872">actions/setup-node#872</a></li>
<li>Update toolkit cache and core by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> and <a href="https://github.com/seongwon-privatenote"><code>@​seongwon-privatenote</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/875">actions/setup-node#875</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-node/compare/v3...v3.8.2">https://github.com/actions/setup-node/compare/v3...v3.8.2</a></p>
<h2>v3.8.1</h2>
<h2>What's Changed</h2>
<p>In scope of this release, the filter was removed within the cache-save step by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/831">actions/setup-node#831</a>. It is filtered and checked in the toolkit/cache library.</p>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-node/compare/v3...v3.8.1">https://github.com/actions/setup-node/compare/v3...v3.8.1</a></p>
<h2>v3.8.0</h2>
<h2>What's Changed</h2>
<h3>Bug fixes:</h3>
<ul>
<li>Add check for existing paths by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/803">actions/setup-node#803</a></li>
<li>Resolve SymbolicLink by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/809">actions/setup-node#809</a></li>
<li>Change passing logic for cache input by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/816">actions/setup-node#816</a></li>
<li>Fix armv7 cache issue by <a href="https://github.com/louislam"><code>@​louislam</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/794">actions/setup-node#794</a></li>
<li>Update check-dist workflow name by <a href="https://github.com/sinchang"><code>@​sinchang</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/710">actions/setup-node#710</a></li>
</ul>
<h3>Feature implementations:</h3>
<ul>
<li>feat: handling the case where &quot;node&quot; is used for tool-versions file. by <a href="https://github.com/xytis"><code>@​xytis</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/812">actions/setup-node#812</a></li>
</ul>
<h3>Documentation changes:</h3>
<ul>
<li>Refer to semver package name in README.md by <a href="https://github.com/olleolleolle"><code>@​olleolleolle</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/808">actions/setup-node#808</a></li>
</ul>
<h3>Update dependencies:</h3>
<ul>
<li>Update toolkit cache to fix zstd by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/804">actions/setup-node#804</a></li>
<li>Bump tough-cookie and <code>@​azure/ms-rest-js</code> by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/802">actions/setup-node#802</a></li>
<li>Bump semver from 6.1.2 to 6.3.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-node/pull/807">actions/setup-node#807</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-node/commit/8f152de45cc393bb48ce5d89d36b731f54556e65"><code>8f152de</code></a> Update actions/checkout for documentation and yaml (<a href="https://redirect.github.com/actions/setup-node/issues/876">#876</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/23755b521f87533c8ed7f8fb13674f9021579e34"><code>23755b5</code></a> upgrade actions/checkout to v4 (<a href="https://redirect.github.com/actions/setup-node/issues/868">#868</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/54534a2a9ba7308e8a8995af3104899e6a95b681"><code>54534a2</code></a> Change node version for action to node20 (<a href="https://redirect.github.com/actions/setup-node/issues/866">#866</a>)</li>
<li>See full diff in <a href="https://github.com/actions/setup-node/compare/v3...v4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions


</details>

sweep-ai[bot]: ## Apply [Sweep Rules](https://docs.sweep.dev/usage/config#rules) to your PR?
- [ ] **Apply:**  Leftover TODOs in the code should be handled.
- [ ] **Apply:**  All new business logic should have corresponding unit tests in the tests/ directory.
- [ ] **Apply:**  Any clearly inefficient or repeated code should be optimized or refactored.

Patch:
diff --git a/.github/workflows/opencommit.yml b/.github/workflows/opencommit.yml
index db244705c..6496bd2ce 100644
--- a/.github/workflows/opencommit.yml
+++ b/.github/workflows/opencommit.yml
@@ -27,7 +27,7 @@ jobs:
     permissions: write-all
     steps:
       - name: Setup Node.js Environment
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
         with:
           node-version: '16'
       - uses: actions/checkout@v4
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 8094718c6..045f269d6 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -42,7 +42,7 @@ jobs:
           git config user.name 'openimbot'
           git config user.email 'openimsdk@qq.com'
           git checkout -b cicd/patch-${{ github.event.number }}
-      - uses: actions/setup-node@v3
+      - uses: actions/setup-node@v4
       - name: Setup Go
         uses: actions/setup-go@v4
       - name: Run go modules tidy
diff --git a/pkg/common/db/relation/mysql_init.go b/pkg/common/db/relation/mysql_init.go
index 0e5ea5e43..16b8c99fa 100644
--- a/pkg/common/db/relation/mysql_init.go
+++ b/pkg/common/db/relation/mysql_init.go
@@ -18,15 +18,12 @@ import (
 	"fmt"
 	"time"
 
-	mysqldriver "github.com/go-sql-driver/mysql"
-	"gorm.io/driver/mysql"
-
 	"github.com/OpenIMSDK/tools/errs"
 	"github.com/OpenIMSDK/tools/log"
 	"github.com/OpenIMSDK/tools/mw/specialerror"
-
+	mysqldriver "github.com/go-sql-driver/mysql"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
-
+	"gorm.io/driver/mysql"
 	"gorm.io/gorm"
 	"gorm.io/gorm/logger"
 )
@@ -35,56 +32,80 @@ const (
 	maxRetry = 100 // number of retries
 )
 
-// newMysqlGormDB Initialize the database connection.
-func newMysqlGormDB() (*gorm.DB, error) {
-	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
-		config.Config.Mysql.Username, config.Config.Mysql.Password, config.Config.Mysql.Address[0], "mysql")
+type option struct {
+	Username      string
+	Password      string
+	Address       []string
+	Database      string
+	LogLevel      int
+	SlowThreshold int
+	MaxLifeTime   int
+	MaxOpenConn   int
+	MaxIdleConn   int
+	Connect       func(dsn string, maxRetry int) (*gorm.DB, error)
+}
 
-	db, err := connectToDatabase(dsn, maxRetry)
-	if err != nil {
-		panic(err.Error() + " Open failed " + dsn)
-	}
-	sqlDB, err := db.DB()
+// newMysqlGormDB Initialize the database connection.
+func newMysqlGormDB(o *option) (*gorm.DB, error) {
+	err := maybeCreateTable(o)
 	if err != nil {
 		return nil, err
 	}
-	defer sqlDB.Close()
-	sql := fmt.Sprintf(
-		"CREATE DATABASE IF NOT EXISTS %s default charset utf8mb4 COLLATE utf8mb4_unicode_ci;",
-		config.Config.Mysql.Database,
-	)
-	err = db.Exec(sql).Error
-	if err != nil {
-		return nil, fmt.Errorf("init db %w", err)
-	}
-	dsn = fmt.Sprintf(
-		"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
-		config.Config.Mysql.Username,
-		config.Config.Mysql.Password,
-		config.Config.Mysql.Address[0],
-		config.Config.Mysql.Database,
-	)
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
+		o.Username, o.Password, o.Address[0], o.Database)
 	sqlLogger := log.NewSqlLogger(
-		logger.LogLevel(config.Config.Mysql.LogLevel),
+		logger.LogLevel(o.LogLevel),
 		true,
-		time.Duration(config.Config.Mysql.SlowThreshold)*time.Millisecond,
+		time.Duration(o.SlowThreshold)*time.Millisecond,
 	)
-	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
+	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
 		Logger: sqlLogger,
 	})
 	if err != nil {
 		return nil, err
 	}
-	sqlDB, err = db.DB()
+	sqlDB, err := db.DB()
 	if err != nil {
 		return nil, err
 	}
-	sqlDB.SetConnMaxLifetime(time.Second * time.Duration(config.Config.Mysql.MaxLifeTime))
-	sqlDB.SetMaxOpenConns(config.Config.Mysql.MaxOpenConn)
-	sqlDB.SetMaxIdleConns(config.Config.Mysql.MaxIdleConn)
+	sqlDB.SetConnMaxLifetime(time.Second * time.Duration(o.MaxLifeTime))
+	sqlDB.SetMaxOpenConns(o.MaxOpenConn)
+	sqlDB.SetMaxIdleConns(o.MaxIdleConn)
 	return db, nil
 }
 
+// maybeCreateTable creates a database if it does not exists.
+func maybeCreateTable(o *option) error {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
+		o.Username, o.Password, o.Address[0], "mysql")
+
+	var db *gorm.DB
+	var err error
+	if f := o.Connect; f != nil {
+		db, err = f(dsn, maxRetry)
+	} else {
+		db, err = connectToDatabase(dsn, maxRetry)
+	}
+	if err != nil {
+		panic(err.Error() + " Open failed " + dsn)
+	}
+
+	sqlDB, err := db.DB()
+	if err != nil {
+		return err
+	}
+	defer sqlDB.Close()
+	sql := fmt.Sprintf(
+		"CREATE DATABASE IF NOT EXISTS `%s` default charset utf8mb4 COLLATE utf8mb4_unicode_ci",
+		o.Database,
+	)
+	err = db.Exec(sql).Error
+	if err != nil {
+		return fmt.Errorf("init db %w", err)
+	}
+	return nil
+}
+
 // connectToDatabase Connection retry for mysql.
 func connectToDatabase(dsn string, maxRetry int) (*gorm.DB, error) {
 	var db *gorm.DB
@@ -106,7 +127,18 @@ func connectToDatabase(dsn string, maxRetry int) (*gorm.DB, error) {
 func NewGormDB() (*gorm.DB, error) {
 	specialerror.AddReplace(gorm.ErrRecordNotFound, errs.ErrRecordNotFound)
 	specialerror.AddErrHandler(replaceDuplicateKey)
-	return newMysqlGormDB()
+
+	return newMysqlGormDB(&option{
+		Username:      config.Config.Mysql.Username,
+		Password:      config.Config.Mysql.Password,
+		Address:       config.Config.Mysql.Address,
+		Database:      config.Config.Mysql.Database,
+		LogLevel:      config.Config.Mysql.LogLevel,
+		SlowThreshold: config.Config.Mysql.SlowThreshold,
+		MaxLifeTime:   config.Config.Mysql.MaxLifeTime,
+		MaxOpenConn:   config.Config.Mysql.MaxOpenConn,
+		MaxIdleConn:   config.Config.Mysql.MaxIdleConn,
+	})
 }
 
 func replaceDuplicateKey(err error) errs.CodeError {


Test patch:
diff --git a/pkg/common/db/relation/mysql_init_test.go b/pkg/common/db/relation/mysql_init_test.go
new file mode 100644
index 000000000..c321dfd9f
--- /dev/null
+++ b/pkg/common/db/relation/mysql_init_test.go
@@ -0,0 +1,121 @@
+package relation
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+	"errors"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+)
+
+func TestMaybeCreateTable(t *testing.T) {
+	t.Run("normal", func(t *testing.T) {
+		err := maybeCreateTable(&option{
+			Username:      "root",
+			Password:      "openIM123",
+			Address:       []string{"172.28.0.1:13306"},
+			Database:      "openIM_v3",
+			LogLevel:      4,
+			SlowThreshold: 500,
+			MaxOpenConn:   1000,
+			MaxIdleConn:   100,
+			MaxLifeTime:   60,
+			Connect: connect(expectExec{
+				query: "CREATE DATABASE IF NOT EXISTS `openIM_v3` default charset utf8mb4 COLLATE utf8mb4_unicode_ci",
+				args:  nil,
+			}),
+		})
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	t.Run("im-db", func(t *testing.T) {
+		err := maybeCreateTable(&option{
+			Username:      "root",
+			Password:      "openIM123",
+			Address:       []string{"172.28.0.1:13306"},
+			Database:      "im-db",
+			LogLevel:      4,
+			SlowThreshold: 500,
+			MaxOpenConn:   1000,
+			MaxIdleConn:   100,
+			MaxLifeTime:   60,
+			Connect: connect(expectExec{
+				query: "CREATE DATABASE IF NOT EXISTS `im-db` default charset utf8mb4 COLLATE utf8mb4_unicode_ci",
+				args:  nil,
+			}),
+		})
+		if err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	t.Run("err", func(t *testing.T) {
+		e := errors.New("e")
+		err := maybeCreateTable(&option{
+			Username:      "root",
+			Password:      "openIM123",
+			Address:       []string{"172.28.0.1:13306"},
+			Database:      "openIM_v3",
+			LogLevel:      4,
+			SlowThreshold: 500,
+			MaxOpenConn:   1000,
+			MaxIdleConn:   100,
+			MaxLifeTime:   60,
+			Connect: connect(expectExec{
+				err: e,
+			}),
+		})
+		if !errors.Is(err, e) {
+			t.Fatalf("err not is e: %v", err)
+		}
+	})
+}
+
+func connect(e expectExec) func(string, int) (*gorm.DB, error) {
+	return func(string, int) (*gorm.DB, error) {
+		return gorm.Open(mysql.New(mysql.Config{
+			SkipInitializeWithVersion: true,
+			Conn:                      sql.OpenDB(e),
+		}), &gorm.Config{
+			Logger: logger.Discard,
+		})
+	}
+}
+
+type expectExec struct {
+	err   error
+	query string
+	args  []driver.NamedValue
+}
+
+func (c expectExec) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+	if c.err != nil {
+		return nil, c.err
+	}
+	if query != c.query {
+		return nil, fmt.Errorf("query mismatch. expect: %s, got: %s", c.query, query)
+	}
+	if reflect.DeepEqual(args, c.args) {
+		return nil, fmt.Errorf("args mismatch. expect: %v, got: %v", c.args, args)
+	}
+	return noEffectResult{}, nil
+}
+
+func (e expectExec) Connect(context.Context) (driver.Conn, error) { return e, nil }
+func (expectExec) Driver() driver.Driver                          { panic("not implemented") }
+func (expectExec) Prepare(query string) (driver.Stmt, error)      { panic("not implemented") }
+func (expectExec) Close() (e error)                               { return }
+func (expectExec) Begin() (driver.Tx, error)                      { panic("not implemented") }
+
+type noEffectResult struct{}
+
+func (noEffectResult) LastInsertId() (i int64, e error) { return }
+func (noEffectResult) RowsAffected() (i int64, e error) { return }