Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
7c708ee2
Unverified
Commit
7c708ee2
authored
Mar 30, 2026
by
boojack
Committed by
GitHub
Mar 30, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: add migration upgrade coverage (#5796)
parent
e520b637
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
437 additions
and
5 deletions
+437
-5
memo_share_service_test.go
server/router/api/v1/test/memo_share_service_test.go
+106
-0
containers.go
store/test/containers.go
+57
-5
migrator_upgrade_test.go
store/test/migrator_upgrade_test.go
+274
-0
No files found.
server/router/api/v1/test/memo_share_service_test.go
View file @
7c708ee2
...
@@ -4,12 +4,14 @@ import (
...
@@ -4,12 +4,14 @@ import (
"context"
"context"
"strings"
"strings"
"testing"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/grpc/status"
apiv1
"github.com/usememos/memos/proto/gen/api/v1"
apiv1
"github.com/usememos/memos/proto/gen/api/v1"
"github.com/usememos/memos/store"
)
)
func
TestDeleteMemoShare_VerifiesShareBelongsToMemo
(
t
*
testing
.
T
)
{
func
TestDeleteMemoShare_VerifiesShareBelongsToMemo
(
t
*
testing
.
T
)
{
...
@@ -107,3 +109,107 @@ func TestGetMemoByShare_IncludesReactions(t *testing.T) {
...
@@ -107,3 +109,107 @@ func TestGetMemoByShare_IncludesReactions(t *testing.T) {
require
.
Equal
(
t
,
"👍"
,
sharedMemo
.
Reactions
[
0
]
.
ReactionType
)
require
.
Equal
(
t
,
"👍"
,
sharedMemo
.
Reactions
[
0
]
.
ReactionType
)
require
.
Equal
(
t
,
memo
.
Name
,
sharedMemo
.
Reactions
[
0
]
.
ContentId
)
require
.
Equal
(
t
,
memo
.
Name
,
sharedMemo
.
Reactions
[
0
]
.
ContentId
)
}
}
func
TestGetMemoByShare_ReturnsNotFoundForUnknownShare
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
_
,
err
:=
ts
.
Service
.
GetMemoByShare
(
ctx
,
&
apiv1
.
GetMemoByShareRequest
{
ShareId
:
"missing-share-token"
,
})
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
codes
.
NotFound
,
status
.
Code
(
err
))
}
func
TestGetMemoByShare_ReturnsNotFoundForExpiredShare
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"share-expired"
)
require
.
NoError
(
t
,
err
)
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
ID
)
memo
,
err
:=
ts
.
Service
.
CreateMemo
(
userCtx
,
&
apiv1
.
CreateMemoRequest
{
Memo
:
&
apiv1
.
Memo
{
Content
:
"memo with expired share"
,
Visibility
:
apiv1
.
Visibility_PRIVATE
,
},
})
require
.
NoError
(
t
,
err
)
expiredTs
:=
time
.
Now
()
.
Add
(
-
time
.
Hour
)
.
Unix
()
expiredShare
,
err
:=
ts
.
Store
.
CreateMemoShare
(
ctx
,
&
store
.
MemoShare
{
UID
:
"expired-share-token"
,
MemoID
:
parseMemoIDFromNameForTest
(
t
,
ts
,
memo
.
Name
),
CreatorID
:
user
.
ID
,
ExpiresTs
:
&
expiredTs
,
})
require
.
NoError
(
t
,
err
)
_
,
err
=
ts
.
Service
.
GetMemoByShare
(
ctx
,
&
apiv1
.
GetMemoByShareRequest
{
ShareId
:
expiredShare
.
UID
,
})
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
codes
.
NotFound
,
status
.
Code
(
err
))
}
func
TestGetMemoByShare_ReturnsNotFoundForArchivedMemo
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"share-archived"
)
require
.
NoError
(
t
,
err
)
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
ID
)
memoResp
,
err
:=
ts
.
Service
.
CreateMemo
(
userCtx
,
&
apiv1
.
CreateMemoRequest
{
Memo
:
&
apiv1
.
Memo
{
Content
:
"memo that will be archived"
,
Visibility
:
apiv1
.
Visibility_PRIVATE
,
},
})
require
.
NoError
(
t
,
err
)
share
,
err
:=
ts
.
Service
.
CreateMemoShare
(
userCtx
,
&
apiv1
.
CreateMemoShareRequest
{
Parent
:
memoResp
.
Name
,
MemoShare
:
&
apiv1
.
MemoShare
{},
})
require
.
NoError
(
t
,
err
)
memoID
:=
parseMemoIDFromNameForTest
(
t
,
ts
,
memoResp
.
Name
)
memo
,
err
:=
ts
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
ID
:
&
memoID
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
memo
)
archived
:=
store
.
Archived
err
=
ts
.
Store
.
UpdateMemo
(
ctx
,
&
store
.
UpdateMemo
{
ID
:
memo
.
ID
,
RowStatus
:
&
archived
,
})
require
.
NoError
(
t
,
err
)
shareToken
:=
share
.
Name
[
strings
.
LastIndex
(
share
.
Name
,
"/"
)
+
1
:
]
_
,
err
=
ts
.
Service
.
GetMemoByShare
(
ctx
,
&
apiv1
.
GetMemoByShareRequest
{
ShareId
:
shareToken
,
})
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
codes
.
NotFound
,
status
.
Code
(
err
))
}
func
parseMemoIDFromNameForTest
(
t
*
testing
.
T
,
ts
*
TestService
,
memoName
string
)
int32
{
t
.
Helper
()
memoUID
,
ok
:=
strings
.
CutPrefix
(
memoName
,
"memos/"
)
require
.
True
(
t
,
ok
,
"memo name must start with memos/: %s"
,
memoName
)
memo
,
err
:=
ts
.
Store
.
GetMemo
(
context
.
Background
(),
&
store
.
FindMemo
{
UID
:
&
memoUID
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
memo
)
return
memo
.
ID
}
store/test/containers.go
View file @
7c708ee2
...
@@ -4,6 +4,8 @@ import (
...
@@ -4,6 +4,8 @@ import (
"context"
"context"
"database/sql"
"database/sql"
"fmt"
"fmt"
"net"
"net/url"
"os"
"os"
"strings"
"strings"
"sync"
"sync"
...
@@ -12,6 +14,7 @@ import (
...
@@ -12,6 +14,7 @@ import (
"time"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/container"
mysqldriver
"github.com/go-sql-driver/mysql"
"github.com/pkg/errors"
"github.com/pkg/errors"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/mysql"
"github.com/testcontainers/testcontainers-go/modules/mysql"
...
@@ -20,7 +23,6 @@ import (
...
@@ -20,7 +23,6 @@ import (
"github.com/testcontainers/testcontainers-go/wait"
"github.com/testcontainers/testcontainers-go/wait"
// Database drivers for connection verification.
// Database drivers for connection verification.
_
"github.com/go-sql-driver/mysql"
_
"github.com/lib/pq"
_
"github.com/lib/pq"
)
)
...
@@ -31,6 +33,9 @@ const (
...
@@ -31,6 +33,9 @@ const (
// Memos container settings for migration testing.
// Memos container settings for migration testing.
MemosDockerImage
=
"neosmemo/memos"
MemosDockerImage
=
"neosmemo/memos"
StableMemosVersion
=
"stable"
// Always points to the latest stable release
StableMemosVersion
=
"stable"
// Always points to the latest stable release
mysqlNetworkAlias
=
"memos-mysql"
postgresNetworkAlias
=
"memos-postgres"
)
)
var
(
var
(
...
@@ -62,12 +67,23 @@ func getTestNetwork(ctx context.Context) (*testcontainers.DockerNetwork, error)
...
@@ -62,12 +67,23 @@ func getTestNetwork(ctx context.Context) (*testcontainers.DockerNetwork, error)
return
testDockerNetwork
.
Load
(),
networkErr
return
testDockerNetwork
.
Load
(),
networkErr
}
}
func
requireTestNetwork
(
ctx
context
.
Context
)
(
*
testcontainers
.
DockerNetwork
,
error
)
{
nw
,
err
:=
getTestNetwork
(
ctx
)
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to create test network"
)
}
if
nw
==
nil
{
return
nil
,
errors
.
New
(
"test network is unavailable"
)
}
return
nw
,
nil
}
// GetMySQLDSN starts a MySQL container (if not already running) and creates a fresh database for this test.
// GetMySQLDSN starts a MySQL container (if not already running) and creates a fresh database for this test.
func
GetMySQLDSN
(
t
*
testing
.
T
)
string
{
func
GetMySQLDSN
(
t
*
testing
.
T
)
string
{
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
mysqlOnce
.
Do
(
func
()
{
mysqlOnce
.
Do
(
func
()
{
nw
,
err
:=
get
TestNetwork
(
ctx
)
nw
,
err
:=
require
TestNetwork
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"failed to create test network: %v"
,
err
)
t
.
Fatalf
(
"failed to create test network: %v"
,
err
)
}
}
...
@@ -86,7 +102,7 @@ func GetMySQLDSN(t *testing.T) string {
...
@@ -86,7 +102,7 @@ func GetMySQLDSN(t *testing.T) string {
wait
.
ForListeningPort
(
"3306/tcp"
),
wait
.
ForListeningPort
(
"3306/tcp"
),
)
.
WithDeadline
(
120
*
time
.
Second
),
)
.
WithDeadline
(
120
*
time
.
Second
),
),
),
network
.
WithNetwork
(
nil
,
nw
),
network
.
WithNetwork
(
[]
string
{
mysqlNetworkAlias
}
,
nw
),
)
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"failed to start MySQL container: %v"
,
err
)
t
.
Fatalf
(
"failed to start MySQL container: %v"
,
err
)
...
@@ -167,7 +183,7 @@ func GetPostgresDSN(t *testing.T) string {
...
@@ -167,7 +183,7 @@ func GetPostgresDSN(t *testing.T) string {
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
postgresOnce
.
Do
(
func
()
{
postgresOnce
.
Do
(
func
()
{
nw
,
err
:=
get
TestNetwork
(
ctx
)
nw
,
err
:=
require
TestNetwork
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"failed to create test network: %v"
,
err
)
t
.
Fatalf
(
"failed to create test network: %v"
,
err
)
}
}
...
@@ -183,7 +199,7 @@ func GetPostgresDSN(t *testing.T) string {
...
@@ -183,7 +199,7 @@ func GetPostgresDSN(t *testing.T) string {
wait
.
ForListeningPort
(
"5432/tcp"
),
wait
.
ForListeningPort
(
"5432/tcp"
),
)
.
WithDeadline
(
120
*
time
.
Second
),
)
.
WithDeadline
(
120
*
time
.
Second
),
),
),
network
.
WithNetwork
(
nil
,
nw
),
network
.
WithNetwork
(
[]
string
{
postgresNetworkAlias
}
,
nw
),
)
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"failed to start PostgreSQL container: %v"
,
err
)
t
.
Fatalf
(
"failed to start PostgreSQL container: %v"
,
err
)
...
@@ -264,6 +280,11 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
...
@@ -264,6 +280,11 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
"MEMOS_MODE"
:
"prod"
,
"MEMOS_MODE"
:
"prod"
,
}
}
nw
,
err
:=
requireTestNetwork
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
var
opts
[]
testcontainers
.
ContainerCustomizer
var
opts
[]
testcontainers
.
ContainerCustomizer
switch
cfg
.
Driver
{
switch
cfg
.
Driver
{
...
@@ -272,6 +293,12 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
...
@@ -272,6 +293,12 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
opts
=
append
(
opts
,
testcontainers
.
WithHostConfigModifier
(
func
(
hc
*
container
.
HostConfig
)
{
opts
=
append
(
opts
,
testcontainers
.
WithHostConfigModifier
(
func
(
hc
*
container
.
HostConfig
)
{
hc
.
Binds
=
append
(
hc
.
Binds
,
fmt
.
Sprintf
(
"%s:%s"
,
cfg
.
DataDir
,
"/var/opt/memos"
))
hc
.
Binds
=
append
(
hc
.
Binds
,
fmt
.
Sprintf
(
"%s:%s"
,
cfg
.
DataDir
,
"/var/opt/memos"
))
}))
}))
case
"mysql"
,
"postgres"
:
if
cfg
.
DSN
==
""
{
return
nil
,
errors
.
Errorf
(
"dsn is required for %s migration testing"
,
cfg
.
Driver
)
}
env
[
"MEMOS_DRIVER"
]
=
cfg
.
Driver
env
[
"MEMOS_DSN"
]
=
cfg
.
DSN
default
:
default
:
return
nil
,
errors
.
Errorf
(
"unsupported driver for migration testing: %s"
,
cfg
.
Driver
)
return
nil
,
errors
.
Errorf
(
"unsupported driver for migration testing: %s"
,
cfg
.
Driver
)
}
}
...
@@ -303,6 +330,7 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
...
@@ -303,6 +330,7 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
}
}
// Apply options
// Apply options
opts
=
append
(
opts
,
network
.
WithNetwork
(
nil
,
nw
))
for
_
,
opt
:=
range
opts
{
for
_
,
opt
:=
range
opts
{
if
err
:=
opt
.
Customize
(
&
genericReq
);
err
!=
nil
{
if
err
:=
opt
.
Customize
(
&
genericReq
);
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to apply container option"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to apply container option"
)
...
@@ -316,3 +344,27 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
...
@@ -316,3 +344,27 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
return
ctr
,
nil
return
ctr
,
nil
}
}
func
getContainerDSN
(
driver
,
hostDSN
string
)
(
string
,
error
)
{
switch
driver
{
case
"mysql"
:
cfg
,
err
:=
mysqldriver
.
ParseDSN
(
hostDSN
)
if
err
!=
nil
{
return
""
,
errors
.
Wrap
(
err
,
"failed to parse mysql dsn"
)
}
cfg
.
Net
=
"tcp"
cfg
.
Addr
=
net
.
JoinHostPort
(
mysqlNetworkAlias
,
"3306"
)
return
cfg
.
FormatDSN
(),
nil
case
"postgres"
:
u
,
err
:=
url
.
Parse
(
hostDSN
)
if
err
!=
nil
{
return
""
,
errors
.
Wrap
(
err
,
"failed to parse postgres dsn"
)
}
u
.
Host
=
net
.
JoinHostPort
(
postgresNetworkAlias
,
"5432"
)
return
u
.
String
(),
nil
case
"sqlite"
:
return
hostDSN
,
nil
default
:
return
""
,
errors
.
Errorf
(
"unsupported driver for container dsn: %s"
,
driver
)
}
}
store/test/migrator_upgrade_test.go
0 → 100644
View file @
7c708ee2
package
test
import
(
"context"
"database/sql"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
storepb
"github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func
TestMigrationFromV0262PreservesLegacyData
(
t
*
testing
.
T
)
{
if
testing
.
Short
()
{
t
.
Skip
(
"skipping container-based upgrade test in short mode"
)
}
if
os
.
Getenv
(
"SKIP_CONTAINER_TESTS"
)
==
"1"
{
t
.
Skip
(
"skipping container-based test (SKIP_CONTAINER_TESTS=1)"
)
}
ctx
:=
context
.
Background
()
driver
:=
getDriverFromEnv
()
cfg
,
hostDSN
:=
prepareV0262MigrationTest
(
t
,
driver
)
t
.
Logf
(
"Starting Memos %s container for %s schema bootstrap..."
,
cfg
.
Version
,
driver
)
container
,
err
:=
StartMemosContainer
(
ctx
,
cfg
)
require
.
NoError
(
t
,
err
,
"failed to start v0.26.2 memos container"
)
t
.
Cleanup
(
func
()
{
if
container
!=
nil
{
_
=
container
.
Terminate
(
ctx
)
}
})
legacyStore
:=
NewTestingStoreWithDSN
(
ctx
,
t
,
driver
,
hostDSN
)
require
.
Eventually
(
t
,
func
()
bool
{
setting
,
err
:=
legacyStore
.
GetInstanceBasicSetting
(
ctx
)
return
err
==
nil
&&
setting
!=
nil
&&
setting
.
SchemaVersion
!=
""
},
45
*
time
.
Second
,
500
*
time
.
Millisecond
,
"legacy schema should be initialized by old container"
)
settingBeforeSeed
,
err
:=
legacyStore
.
GetInstanceBasicSetting
(
ctx
)
require
.
NoError
(
t
,
err
)
t
.
Logf
(
"Legacy schema version before migration: %s"
,
settingBeforeSeed
.
SchemaVersion
)
err
=
container
.
Terminate
(
ctx
)
require
.
NoError
(
t
,
err
,
"failed to stop v0.26.2 memos container"
)
container
=
nil
db
:=
openMigrationSQLDB
(
t
,
driver
,
hostDSN
)
defer
db
.
Close
()
seedLegacyMigrationData
(
ctx
,
t
,
driver
,
db
)
count
,
err
:=
countSystemSetting
(
ctx
,
db
,
"STORAGE"
)
require
.
NoError
(
t
,
err
)
require
.
Zero
(
t
,
count
,
"v0.26.2 database should not have a STORAGE setting before migration"
)
ts
:=
NewTestingStoreWithDSN
(
ctx
,
t
,
driver
,
hostDSN
)
err
=
ts
.
Migrate
(
ctx
)
require
.
NoError
(
t
,
err
,
"migration from v0.26.2 should succeed for %s"
,
driver
)
currentVersion
,
err
:=
ts
.
GetCurrentSchemaVersion
()
require
.
NoError
(
t
,
err
)
currentSetting
,
err
:=
ts
.
GetInstanceBasicSetting
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
currentVersion
,
currentSetting
.
SchemaVersion
,
"schema version should be updated"
)
storageSetting
,
err
:=
ts
.
GetInstanceStorageSetting
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
storepb
.
InstanceStorageSetting_DATABASE
,
storageSetting
.
StorageType
,
"existing installs should stay on DATABASE storage"
)
idps
,
err
:=
ts
.
ListIdentityProviders
(
ctx
,
&
store
.
FindIdentityProvider
{})
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
idps
,
2
)
idpUIDsByName
:=
map
[
string
]
string
{}
for
_
,
idp
:=
range
idps
{
idpUIDsByName
[
idp
.
Name
]
=
idp
.
Uid
}
require
.
Equal
(
t
,
"00000191"
,
idpUIDsByName
[
"Legacy Google"
])
require
.
Equal
(
t
,
"00000192"
,
idpUIDsByName
[
"Legacy GitHub"
])
inboxes
,
err
:=
ts
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{})
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
inboxes
,
1
)
require
.
NotNil
(
t
,
inboxes
[
0
]
.
Message
)
require
.
Equal
(
t
,
storepb
.
InboxMessage_MEMO_COMMENT
,
inboxes
[
0
]
.
Message
.
Type
)
require
.
Equal
(
t
,
int32
(
102
),
inboxes
[
0
]
.
Message
.
GetMemoComment
()
.
MemoId
)
require
.
Equal
(
t
,
int32
(
101
),
inboxes
[
0
]
.
Message
.
GetMemoComment
()
.
RelatedMemoId
)
activityExists
,
err
:=
tableExists
(
ctx
,
db
,
driver
,
"activity"
)
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
activityExists
,
"activity table should be removed after migration"
)
memoShareExists
,
err
:=
tableExists
(
ctx
,
db
,
driver
,
"memo_share"
)
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
memoShareExists
,
"memo_share table should be created"
)
share
,
err
:=
ts
.
CreateMemoShare
(
ctx
,
&
store
.
MemoShare
{
UID
:
"post-upgrade-share"
,
MemoID
:
101
,
CreatorID
:
11
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
"post-upgrade-share"
,
share
.
UID
)
postUpgradeUser
,
err
:=
createTestingUserWithRole
(
ctx
,
ts
,
"postupgrade"
,
store
.
RoleUser
)
require
.
NoError
(
t
,
err
)
postUpgradeMemo
,
err
:=
ts
.
CreateMemo
(
ctx
,
&
store
.
Memo
{
UID
:
"post-upgrade-memo-v0262"
,
CreatorID
:
postUpgradeUser
.
ID
,
Content
:
"created after v0.26.2 migration"
,
Visibility
:
store
.
Public
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
"created after v0.26.2 migration"
,
postUpgradeMemo
.
Content
)
}
func
prepareV0262MigrationTest
(
t
*
testing
.
T
,
driver
string
)
(
MemosContainerConfig
,
string
)
{
t
.
Helper
()
const
version
=
"0.26.2"
switch
driver
{
case
"sqlite"
:
dataDir
:=
t
.
TempDir
()
return
MemosContainerConfig
{
Version
:
version
,
Driver
:
driver
,
DataDir
:
dataDir
,
},
fmt
.
Sprintf
(
"%s/memos_prod.db"
,
dataDir
)
case
"mysql"
:
hostDSN
:=
GetMySQLDSN
(
t
)
containerDSN
,
err
:=
getContainerDSN
(
driver
,
hostDSN
)
require
.
NoError
(
t
,
err
)
return
MemosContainerConfig
{
Version
:
version
,
Driver
:
driver
,
DSN
:
containerDSN
,
},
hostDSN
case
"postgres"
:
hostDSN
:=
GetPostgresDSN
(
t
)
containerDSN
,
err
:=
getContainerDSN
(
driver
,
hostDSN
)
require
.
NoError
(
t
,
err
)
return
MemosContainerConfig
{
Version
:
version
,
Driver
:
driver
,
DSN
:
containerDSN
,
},
hostDSN
default
:
t
.
Fatalf
(
"unsupported driver: %s"
,
driver
)
return
MemosContainerConfig
{},
""
}
}
func
openMigrationSQLDB
(
t
*
testing
.
T
,
driver
,
dsn
string
)
*
sql
.
DB
{
t
.
Helper
()
db
,
err
:=
sql
.
Open
(
driver
,
dsn
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
db
.
Ping
())
return
db
}
func
seedLegacyMigrationData
(
ctx
context
.
Context
,
t
*
testing
.
T
,
driver
string
,
db
*
sql
.
DB
)
{
t
.
Helper
()
execMigrationSQL
(
t
,
db
,
legacyInsertUserSQL
(
driver
,
11
,
"owner"
))
execMigrationSQL
(
t
,
db
,
legacyInsertUserSQL
(
driver
,
12
,
"commenter"
))
execMigrationSQL
(
t
,
db
,
legacyInsertMemoSQL
(
101
,
11
,
"legacy-parent"
,
"parent memo"
))
execMigrationSQL
(
t
,
db
,
legacyInsertMemoSQL
(
102
,
12
,
"legacy-comment"
,
"comment memo"
))
execMigrationSQL
(
t
,
db
,
legacyInsertActivitySQL
(
201
,
12
))
execMigrationSQL
(
t
,
db
,
legacyInsertInboxSQL
(
301
,
12
,
11
,
201
))
execMigrationSQL
(
t
,
db
,
legacyInsertIDPSQL
(
401
,
"Legacy Google"
))
execMigrationSQL
(
t
,
db
,
legacyInsertIDPSQL
(
402
,
"Legacy GitHub"
))
var
message
string
err
:=
db
.
QueryRowContext
(
ctx
,
"SELECT message FROM inbox WHERE id = 301"
)
.
Scan
(
&
message
)
require
.
NoError
(
t
,
err
)
require
.
Contains
(
t
,
message
,
"
\"
activityId
\"
:201"
)
require
.
NotContains
(
t
,
message
,
"
\"
memoComment
\"
"
)
}
func
execMigrationSQL
(
t
*
testing
.
T
,
db
*
sql
.
DB
,
query
string
)
{
t
.
Helper
()
_
,
err
:=
db
.
Exec
(
query
)
require
.
NoError
(
t
,
err
,
"failed to execute SQL: %s"
,
query
)
}
func
legacyInsertUserSQL
(
driver
string
,
id
int
,
username
string
)
string
{
table
:=
"user"
switch
driver
{
case
"mysql"
:
table
=
"`user`"
case
"postgres"
,
"sqlite"
:
table
=
`"user"`
default
:
// Keep the unquoted fallback for unknown test drivers.
}
return
fmt
.
Sprintf
(
"INSERT INTO %s (id, username, role, email, nickname, password_hash, avatar_url, description) VALUES (%d, '%s', 'USER', '%s@example.com', '%s', 'legacy-hash', '', 'legacy user')"
,
table
,
id
,
username
,
username
,
username
,
)
}
func
legacyInsertMemoSQL
(
id
,
creatorID
int
,
uid
,
content
string
)
string
{
payload
:=
"{}"
return
fmt
.
Sprintf
(
"INSERT INTO memo (id, uid, creator_id, content, visibility, payload) VALUES (%d, '%s', %d, '%s', 'PRIVATE', '%s')"
,
id
,
uid
,
creatorID
,
content
,
payload
,
)
}
func
legacyInsertActivitySQL
(
id
,
creatorID
int
)
string
{
payload
:=
`{"memoComment":{"memoId":102,"relatedMemoId":101}}`
return
fmt
.
Sprintf
(
"INSERT INTO activity (id, creator_id, type, level, payload) VALUES (%d, %d, 'MEMO_COMMENT', 'INFO', '%s')"
,
id
,
creatorID
,
payload
,
)
}
func
legacyInsertInboxSQL
(
id
,
senderID
,
receiverID
,
activityID
int
)
string
{
message
:=
fmt
.
Sprintf
(
`{"type":"MEMO_COMMENT","activityId":%d}`
,
activityID
)
return
fmt
.
Sprintf
(
"INSERT INTO inbox (id, sender_id, receiver_id, status, message) VALUES (%d, %d, %d, 'UNREAD', '%s')"
,
id
,
senderID
,
receiverID
,
message
,
)
}
func
legacyInsertIDPSQL
(
id
int
,
name
string
)
string
{
config
:=
`{"clientId":"legacy-client","clientSecret":"legacy-secret","authUrl":"https://example.com/auth","tokenUrl":"https://example.com/token","userInfoUrl":"https://example.com/userinfo"}`
return
fmt
.
Sprintf
(
"INSERT INTO idp (id, name, type, identifier_filter, config) VALUES (%d, '%s', 'OAUTH2', '', '%s')"
,
id
,
name
,
config
,
)
}
func
countSystemSetting
(
ctx
context
.
Context
,
db
*
sql
.
DB
,
name
string
)
(
int
,
error
)
{
var
count
int
err
:=
db
.
QueryRowContext
(
ctx
,
"SELECT COUNT(*) FROM system_setting WHERE name = ?"
,
name
)
.
Scan
(
&
count
)
if
err
==
nil
{
return
count
,
nil
}
err
=
db
.
QueryRowContext
(
ctx
,
"SELECT COUNT(*) FROM system_setting WHERE name = $1"
,
name
)
.
Scan
(
&
count
)
return
count
,
err
}
func
tableExists
(
ctx
context
.
Context
,
db
*
sql
.
DB
,
driver
,
table
string
)
(
bool
,
error
)
{
switch
driver
{
case
"sqlite"
:
var
name
string
err
:=
db
.
QueryRowContext
(
ctx
,
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?"
,
table
)
.
Scan
(
&
name
)
if
err
==
sql
.
ErrNoRows
{
return
false
,
nil
}
return
err
==
nil
,
err
case
"mysql"
:
var
count
int
err
:=
db
.
QueryRowContext
(
ctx
,
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?"
,
table
)
.
Scan
(
&
count
)
return
count
>
0
,
err
case
"postgres"
:
var
regclass
sql
.
NullString
err
:=
db
.
QueryRowContext
(
ctx
,
"SELECT to_regclass($1)"
,
"public."
+
table
)
.
Scan
(
&
regclass
)
return
regclass
.
Valid
&&
strings
.
EqualFold
(
regclass
.
String
,
table
),
err
default
:
return
false
,
errors
.
Errorf
(
"unsupported driver: %s"
,
driver
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment