init commit

This commit is contained in:
2025-11-30 13:01:24 -05:00
parent f4596a372d
commit 29355260ed
607 changed files with 136371 additions and 234 deletions

13
store/test/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Store tests
## How to test store with MySQL?
1. Create a database in your MySQL server.
2. Run the following command with two environment variables set:
```go
DRIVER=mysql DSN=root@/memos_test go test -v ./test/store/...
```
- `DRIVER` should be set to `mysql`.
- `DSN` should be set to the DSN of your MySQL server.

View File

@@ -0,0 +1,34 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestActivityStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
create := &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
}
activity, err := ts.CreateActivity(ctx, create)
require.NoError(t, err)
require.NotNil(t, activity)
activities, err := ts.ListActivities(ctx, &store.FindActivity{
ID: &activity.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(activities))
require.Equal(t, activity, activities[0])
ts.Close()
}

View File

@@ -0,0 +1,63 @@
package teststore
import (
"context"
"testing"
"github.com/lithammer/shortuuid/v4"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
)
func TestAttachmentStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
_, err := ts.CreateAttachment(ctx, &store.Attachment{
UID: shortuuid.New(),
CreatorID: 101,
Filename: "test.epub",
Blob: []byte("test"),
Type: "application/epub+zip",
Size: 637607,
})
require.NoError(t, err)
correctFilename := "test.epub"
incorrectFilename := "test.png"
attachment, err := ts.GetAttachment(ctx, &store.FindAttachment{
Filename: &correctFilename,
})
require.NoError(t, err)
require.Equal(t, correctFilename, attachment.Filename)
require.Equal(t, int32(1), attachment.ID)
notFoundAttachment, err := ts.GetAttachment(ctx, &store.FindAttachment{
Filename: &incorrectFilename,
})
require.NoError(t, err)
require.Nil(t, notFoundAttachment)
var correctCreatorID int32 = 101
var incorrectCreatorID int32 = 102
_, err = ts.GetAttachment(ctx, &store.FindAttachment{
CreatorID: &correctCreatorID,
})
require.NoError(t, err)
notFoundAttachment, err = ts.GetAttachment(ctx, &store.FindAttachment{
CreatorID: &incorrectCreatorID,
})
require.NoError(t, err)
require.Nil(t, notFoundAttachment)
err = ts.DeleteAttachment(ctx, &store.DeleteAttachment{
ID: 1,
})
require.NoError(t, err)
err = ts.DeleteAttachment(ctx, &store.DeleteAttachment{
ID: 2,
})
require.ErrorContains(t, err, "attachment not found")
ts.Close()
}

60
store/test/idp_test.go Normal file
View File

@@ -0,0 +1,60 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestIdentityProviderStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
createdIDP, err := ts.CreateIdentityProvider(ctx, &storepb.IdentityProvider{
Name: "GitHub OAuth",
Type: storepb.IdentityProvider_OAUTH2,
IdentifierFilter: "",
Config: &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "client_id",
ClientSecret: "client_secret",
AuthUrl: "https://github.com/auth",
TokenUrl: "https://github.com/token",
UserInfoUrl: "https://github.com/user",
Scopes: []string{"login"},
FieldMapping: &storepb.FieldMapping{
Identifier: "login",
DisplayName: "name",
Email: "email",
},
},
},
},
})
require.NoError(t, err)
idp, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{
ID: &createdIDP.Id,
})
require.NoError(t, err)
require.NotNil(t, idp)
require.Equal(t, createdIDP, idp)
newName := "My GitHub OAuth"
updatedIdp, err := ts.UpdateIdentityProvider(ctx, &store.UpdateIdentityProviderV1{
ID: idp.Id,
Name: &newName,
})
require.NoError(t, err)
require.Equal(t, newName, updatedIdp.Name)
err = ts.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{
ID: idp.Id,
})
require.NoError(t, err)
idpList, err := ts.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
require.NoError(t, err)
require.Equal(t, 0, len(idpList))
ts.Close()
}

54
store/test/inbox_test.go Normal file
View File

@@ -0,0 +1,54 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestInboxStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
const systemBotID int32 = 0
create := &store.Inbox{
SenderID: systemBotID,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_MEMO_COMMENT,
},
}
inbox, err := ts.CreateInbox(ctx, create)
require.NoError(t, err)
require.NotNil(t, inbox)
require.Equal(t, create.Message, inbox.Message)
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(inboxes))
require.Equal(t, inbox, inboxes[0])
updatedInbox, err := ts.UpdateInbox(ctx, &store.UpdateInbox{
ID: inbox.ID,
Status: store.ARCHIVED,
})
require.NoError(t, err)
require.NotNil(t, updatedInbox)
require.Equal(t, store.ARCHIVED, updatedInbox.Status)
err = ts.DeleteInbox(ctx, &store.DeleteInbox{
ID: inbox.ID,
})
require.NoError(t, err)
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 0, len(inboxes))
ts.Close()
}

View File

@@ -0,0 +1,62 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
)
func TestMemoRelationStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.Memo{
UID: "main-memo",
CreatorID: user.ID,
Content: "main memo content",
Visibility: store.Public,
}
memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
relatedMemoCreate := &store.Memo{
UID: "related-memo",
CreatorID: user.ID,
Content: "related memo content",
Visibility: store.Public,
}
relatedMemo, err := ts.CreateMemo(ctx, relatedMemoCreate)
require.NoError(t, err)
require.Equal(t, relatedMemoCreate.Content, relatedMemo.Content)
commentMemoCreate := &store.Memo{
UID: "comment-memo",
CreatorID: user.ID,
Content: "comment memo content",
Visibility: store.Public,
}
commentMemo, err := ts.CreateMemo(ctx, commentMemoCreate)
require.NoError(t, err)
require.Equal(t, commentMemoCreate.Content, commentMemo.Content)
// Reference relation.
referenceRelation := &store.MemoRelation{
MemoID: memo.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
}
_, err = ts.UpsertMemoRelation(ctx, referenceRelation)
require.NoError(t, err)
// Comment relation.
commentRelation := &store.MemoRelation{
MemoID: memo.ID,
RelatedMemoID: commentMemo.ID,
Type: store.MemoRelationComment,
}
_, err = ts.UpsertMemoRelation(ctx, commentRelation)
require.NoError(t, err)
ts.Close()
}

118
store/test/memo_test.go Normal file
View File

@@ -0,0 +1,118 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
storepb "github.com/usememos/memos/proto/gen/store"
)
func TestMemoStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.Memo{
UID: "test-resource-name",
CreatorID: user.ID,
Content: "test_content",
Visibility: store.Public,
}
memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
memoPatchContent := "test_content_2"
memoPatch := &store.UpdateMemo{
ID: memo.ID,
Content: &memoPatchContent,
}
err = ts.UpdateMemo(ctx, memoPatch)
require.NoError(t, err)
memo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &memo.ID,
})
require.NoError(t, err)
require.NotNil(t, memo)
memoList, err := ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(memoList))
require.Equal(t, memo, memoList[0])
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo.ID,
})
require.NoError(t, err)
memoList, err = ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 0, len(memoList))
memoList, err = ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID,
VisibilityList: []store.Visibility{store.Public},
})
require.NoError(t, err)
require.Equal(t, 0, len(memoList))
ts.Close()
}
func TestMemoListByTags(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.Memo{
UID: "test-resource-name",
CreatorID: user.ID,
Content: "test_content",
Visibility: store.Public,
Payload: &storepb.MemoPayload{
Tags: []string{"test_tag"},
},
}
memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
memo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &memo.ID,
})
require.NoError(t, err)
require.NotNil(t, memo)
memoList, err := ts.ListMemos(ctx, &store.FindMemo{
PayloadFind: &store.FindMemoPayload{
TagSearch: []string{"test_tag"},
},
})
require.NoError(t, err)
require.Equal(t, 1, len(memoList))
require.Equal(t, memo, memoList[0])
ts.Close()
}
func TestDeleteMemoStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.Memo{
UID: "test-resource-name",
CreatorID: user.ID,
Content: "test_content",
Visibility: store.Public,
}
memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo.ID,
})
require.NoError(t, err)
ts.Close()
}

View File

@@ -0,0 +1,17 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestGetCurrentSchemaVersion(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
currentSchemaVersion, err := ts.GetCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, "0.25.1", currentSchemaVersion)
}

View File

@@ -0,0 +1,48 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/store"
)
func TestReactionStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
contentID := "test_content_id"
reaction, err := ts.UpsertReaction(ctx, &store.Reaction{
CreatorID: user.ID,
ContentID: contentID,
ReactionType: "💗",
})
require.NoError(t, err)
require.NotNil(t, reaction)
require.NotEmpty(t, reaction.ID)
reactions, err := ts.ListReactions(ctx, &store.FindReaction{
ContentID: &contentID,
})
require.NoError(t, err)
require.Len(t, reactions, 1)
require.Equal(t, reaction, reactions[0])
err = ts.DeleteReaction(ctx, &store.DeleteReaction{
ID: reaction.ID,
})
require.NoError(t, err)
reactions, err = ts.ListReactions(ctx, &store.FindReaction{
ContentID: &contentID,
})
require.NoError(t, err)
require.Len(t, reactions, 0)
ts.Close()
}

124
store/test/store.go Normal file
View File

@@ -0,0 +1,124 @@
package teststore
import (
"context"
"fmt"
"log/slog"
"net"
"os"
"testing"
// sqlite driver.
_ "modernc.org/sqlite"
"github.com/joho/godotenv"
"github.com/usememos/memos/internal/profile"
"github.com/usememos/memos/internal/version"
"github.com/usememos/memos/store"
"github.com/usememos/memos/store/db"
)
func NewTestingStore(ctx context.Context, t *testing.T) *store.Store {
profile := getTestingProfile(t)
dbDriver, err := db.NewDBDriver(profile)
if err != nil {
slog.Error("failed to create db driver", slog.String("error", err.Error()))
}
resetTestingDB(ctx, profile, dbDriver)
store := store.New(dbDriver, profile)
if err := store.Migrate(ctx); err != nil {
slog.Error("failed to migrate db", slog.String("error", err.Error()))
}
return store
}
func resetTestingDB(ctx context.Context, profile *profile.Profile, dbDriver store.Driver) {
if profile.Driver == "mysql" {
_, err := dbDriver.GetDB().ExecContext(ctx, `
DROP TABLE IF EXISTS migration_history;
DROP TABLE IF EXISTS system_setting;
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS user_setting;
DROP TABLE IF EXISTS memo;
DROP TABLE IF EXISTS memo_organizer;
DROP TABLE IF EXISTS memo_relation;
DROP TABLE IF EXISTS resource;
DROP TABLE IF EXISTS tag;
DROP TABLE IF EXISTS activity;
DROP TABLE IF EXISTS storage;
DROP TABLE IF EXISTS idp;
DROP TABLE IF EXISTS inbox;
DROP TABLE IF EXISTS reaction;`)
if err != nil {
slog.Error("failed to reset testing db", slog.String("error", err.Error()))
panic(err)
}
} else if profile.Driver == "postgres" {
_, err := dbDriver.GetDB().ExecContext(ctx, `
DROP TABLE IF EXISTS migration_history CASCADE;
DROP TABLE IF EXISTS system_setting CASCADE;
DROP TABLE IF EXISTS "user" CASCADE;
DROP TABLE IF EXISTS user_setting CASCADE;
DROP TABLE IF EXISTS memo CASCADE;
DROP TABLE IF EXISTS memo_organizer CASCADE;
DROP TABLE IF EXISTS memo_relation CASCADE;
DROP TABLE IF EXISTS resource CASCADE;
DROP TABLE IF EXISTS tag CASCADE;
DROP TABLE IF EXISTS activity CASCADE;
DROP TABLE IF EXISTS storage CASCADE;
DROP TABLE IF EXISTS idp CASCADE;
DROP TABLE IF EXISTS inbox CASCADE;
DROP TABLE IF EXISTS reaction CASCADE;`)
if err != nil {
slog.Error("failed to reset testing db", slog.String("error", err.Error()))
panic(err)
}
}
}
func getUnusedPort() int {
// Get a random unused port
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
panic(err)
}
defer listener.Close()
// Get the port number
port := listener.Addr().(*net.TCPAddr).Port
return port
}
func getTestingProfile(t *testing.T) *profile.Profile {
if err := godotenv.Load(".env"); err != nil {
t.Log("failed to load .env file, but it's ok")
}
// Get a temporary directory for the test data.
dir := t.TempDir()
mode := "prod"
port := getUnusedPort()
driver := getDriverFromEnv()
dsn := os.Getenv("DSN")
if driver == "sqlite" {
dsn = fmt.Sprintf("%s/memos_%s.db", dir, mode)
}
return &profile.Profile{
Mode: mode,
Port: port,
Data: dir,
DSN: dsn,
Driver: driver,
Version: version.GetCurrentVersion(mode),
}
}
func getDriverFromEnv() string {
driver := os.Getenv("DRIVER")
if driver == "" {
driver = "sqlite"
}
return driver
}

View File

@@ -0,0 +1,28 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestUserSettingStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
_, err = ts.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSetting_GENERAL,
Value: &storepb.UserSetting_General{General: &storepb.GeneralUserSetting{Locale: "en"}},
})
require.NoError(t, err)
list, err := ts.ListUserSettings(ctx, &store.FindUserSetting{})
require.NoError(t, err)
require.Equal(t, 1, len(list))
ts.Close()
}

56
store/test/user_test.go Normal file
View File

@@ -0,0 +1,56 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
"github.com/usememos/memos/store"
)
func TestUserStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
users, err := ts.ListUsers(ctx, &store.FindUser{})
require.NoError(t, err)
require.Equal(t, 1, len(users))
require.Equal(t, store.RoleHost, users[0].Role)
require.Equal(t, user, users[0])
userPatchNickname := "test_nickname_2"
userPatch := &store.UpdateUser{
ID: user.ID,
Nickname: &userPatchNickname,
}
user, err = ts.UpdateUser(ctx, userPatch)
require.NoError(t, err)
require.Equal(t, userPatchNickname, user.Nickname)
err = ts.DeleteUser(ctx, &store.DeleteUser{
ID: user.ID,
})
require.NoError(t, err)
users, err = ts.ListUsers(ctx, &store.FindUser{})
require.NoError(t, err)
require.Equal(t, 0, len(users))
ts.Close()
}
func createTestingHostUser(ctx context.Context, ts *store.Store) (*store.User, error) {
userCreate := &store.User{
Username: "test",
Role: store.RoleHost,
Email: "test@test.com",
Nickname: "test_nickname",
Description: "test_description",
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte("test_password"), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
userCreate.PasswordHash = string(passwordHash)
user, err := ts.CreateUser(ctx, userCreate)
return user, err
}

View File

@@ -0,0 +1,31 @@
package teststore
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestWorkspaceSettingV1Store(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
workspaceSetting, err := ts.UpsertWorkspaceSetting(ctx, &storepb.WorkspaceSetting{
Key: storepb.WorkspaceSettingKey_GENERAL,
Value: &storepb.WorkspaceSetting_GeneralSetting{
GeneralSetting: &storepb.WorkspaceGeneralSetting{
AdditionalScript: "",
},
},
})
require.NoError(t, err)
setting, err := ts.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{
Name: storepb.WorkspaceSettingKey_GENERAL.String(),
})
require.NoError(t, err)
require.Equal(t, workspaceSetting, setting)
ts.Close()
}