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
af2a2588
Commit
af2a2588
authored
Jan 19, 2026
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore(test): add edge case tests for user settings shortcuts and JSON fields
parent
dc7ec8a8
Changes
5
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
570 additions
and
244 deletions
+570
-244
render.go
plugin/filter/render.go
+1
-1
instance_setting_test.go
store/test/instance_setting_test.go
+46
-0
memo_filter_comprehension_test.go
store/test/memo_filter_comprehension_test.go
+0
-243
memo_filter_test.go
store/test/memo_filter_test.go
+305
-0
user_setting_test.go
store/test/user_setting_test.go
+218
-0
No files found.
plugin/filter/render.go
View file @
af2a2588
...
...
@@ -564,7 +564,7 @@ func (r *renderer) jsonBoolPredicate(field Field) (string, error) {
case
DialectSQLite
:
return
fmt
.
Sprintf
(
"%s IS TRUE"
,
expr
),
nil
case
DialectMySQL
:
return
fmt
.
Sprintf
(
"
%s
= CAST('true' AS JSON)"
,
expr
),
nil
return
fmt
.
Sprintf
(
"
COALESCE(%s, CAST('false' AS JSON))
= CAST('true' AS JSON)"
,
expr
),
nil
case
DialectPostgres
:
return
fmt
.
Sprintf
(
"(%s)::boolean IS TRUE"
,
expr
),
nil
default
:
...
...
store/test/instance_setting_test.go
View file @
af2a2588
...
...
@@ -254,3 +254,49 @@ func TestInstanceSettingListAll(t *testing.T) {
ts
.
Close
()
}
func
TestInstanceSettingEdgeCases
(
t
*
testing
.
T
)
{
t
.
Parallel
()
ctx
:=
context
.
Background
()
ts
:=
NewTestingStore
(
ctx
,
t
)
// Case 1: General Setting with special characters and Unicode
specialScript
:=
`<script>alert("你好"); var x = 'test\'s';</script>`
specialStyle
:=
`body { font-family: "Noto Sans SC", sans-serif; content: "\u2764"; }`
_
,
err
:=
ts
.
UpsertInstanceSetting
(
ctx
,
&
storepb
.
InstanceSetting
{
Key
:
storepb
.
InstanceSettingKey_GENERAL
,
Value
:
&
storepb
.
InstanceSetting_GeneralSetting
{
GeneralSetting
:
&
storepb
.
InstanceGeneralSetting
{
AdditionalScript
:
specialScript
,
AdditionalStyle
:
specialStyle
,
},
},
})
require
.
NoError
(
t
,
err
)
generalSetting
,
err
:=
ts
.
GetInstanceGeneralSetting
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
specialScript
,
generalSetting
.
AdditionalScript
)
require
.
Equal
(
t
,
specialStyle
,
generalSetting
.
AdditionalStyle
)
// Case 2: Memo Related Setting with Unicode reactions
unicodeReactions
:=
[]
string
{
"🐱"
,
"🐶"
,
"🦊"
,
"🦄"
}
_
,
err
=
ts
.
UpsertInstanceSetting
(
ctx
,
&
storepb
.
InstanceSetting
{
Key
:
storepb
.
InstanceSettingKey_MEMO_RELATED
,
Value
:
&
storepb
.
InstanceSetting_MemoRelatedSetting
{
MemoRelatedSetting
:
&
storepb
.
InstanceMemoRelatedSetting
{
ContentLengthLimit
:
1000
,
Reactions
:
unicodeReactions
,
},
},
})
require
.
NoError
(
t
,
err
)
memoSetting
,
err
:=
ts
.
GetInstanceMemoRelatedSetting
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
unicodeReactions
,
memoSetting
.
Reactions
)
ts
.
Close
()
}
store/test/memo_filter_comprehension_test.go
deleted
100644 → 0
View file @
dc7ec8a8
package
test
import
(
"testing"
"github.com/stretchr/testify/require"
)
// =============================================================================
// Tag Comprehension Tests (exists macro)
// Schema: tags (list of strings, supports exists/all macros with predicates)
// =============================================================================
func
TestMemoFilterTagsExistsStartsWith
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create memos with different tags
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-archive1"
,
tc
.
User
.
ID
)
.
Content
(
"Archived project memo"
)
.
Tags
(
"archive/project"
,
"done"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-archive2"
,
tc
.
User
.
ID
)
.
Content
(
"Archived work memo"
)
.
Tags
(
"archive/work"
,
"old"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-active"
,
tc
.
User
.
ID
)
.
Content
(
"Active project memo"
)
.
Tags
(
"project/active"
,
"todo"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-homelab"
,
tc
.
User
.
ID
)
.
Content
(
"Homelab memo"
)
.
Tags
(
"homelab/memos"
,
"tech"
))
// Test: tags.exists(t, t.startsWith("archive")) - should match archived memos
memos
:=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive"))`
)
require
.
Len
(
t
,
memos
,
2
,
"Should find 2 archived memos"
)
for
_
,
memo
:=
range
memos
{
hasArchiveTag
:=
false
for
_
,
tag
:=
range
memo
.
Payload
.
Tags
{
if
len
(
tag
)
>=
7
&&
tag
[
:
7
]
==
"archive"
{
hasArchiveTag
=
true
break
}
}
require
.
True
(
t
,
hasArchiveTag
,
"Memo should have tag starting with 'archive'"
)
}
// Test: !tags.exists(t, t.startsWith("archive")) - should match non-archived memos
memos
=
tc
.
ListWithFilter
(
`!tags.exists(t, t.startsWith("archive"))`
)
require
.
Len
(
t
,
memos
,
2
,
"Should find 2 non-archived memos"
)
// Test: tags.exists(t, t.startsWith("project")) - should match project memos
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("project"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 project memo"
)
// Test: tags.exists(t, t.startsWith("homelab")) - should match homelab memos
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("homelab"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 homelab memo"
)
// Test: tags.exists(t, t.startsWith("nonexistent")) - should match nothing
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("nonexistent"))`
)
require
.
Len
(
t
,
memos
,
0
,
"Should find no memos"
)
}
func
TestMemoFilterTagsExistsContains
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create memos with different tags
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-todo1"
,
tc
.
User
.
ID
)
.
Content
(
"Todo task 1"
)
.
Tags
(
"project/todo"
,
"urgent"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-todo2"
,
tc
.
User
.
ID
)
.
Content
(
"Todo task 2"
)
.
Tags
(
"work/todo-list"
,
"pending"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-done"
,
tc
.
User
.
ID
)
.
Content
(
"Done task"
)
.
Tags
(
"project/completed"
,
"done"
))
// Test: tags.exists(t, t.contains("todo")) - should match todos
memos
:=
tc
.
ListWithFilter
(
`tags.exists(t, t.contains("todo"))`
)
require
.
Len
(
t
,
memos
,
2
,
"Should find 2 todo memos"
)
// Test: tags.exists(t, t.contains("done")) - should match done
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.contains("done"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 done memo"
)
// Test: !tags.exists(t, t.contains("todo")) - should exclude todos
memos
=
tc
.
ListWithFilter
(
`!tags.exists(t, t.contains("todo"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 non-todo memo"
)
}
func
TestMemoFilterTagsExistsEndsWith
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create memos with different tag endings
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-bug"
,
tc
.
User
.
ID
)
.
Content
(
"Bug report"
)
.
Tags
(
"project/bug"
,
"critical"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-debug"
,
tc
.
User
.
ID
)
.
Content
(
"Debug session"
)
.
Tags
(
"work/debug"
,
"dev"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-feature"
,
tc
.
User
.
ID
)
.
Content
(
"New feature"
)
.
Tags
(
"project/feature"
,
"new"
))
// Test: tags.exists(t, t.endsWith("bug")) - should match bug-related tags
memos
:=
tc
.
ListWithFilter
(
`tags.exists(t, t.endsWith("bug"))`
)
require
.
Len
(
t
,
memos
,
2
,
"Should find 2 bug-related memos"
)
// Test: tags.exists(t, t.endsWith("feature")) - should match feature
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.endsWith("feature"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 feature memo"
)
// Test: !tags.exists(t, t.endsWith("bug")) - should exclude bug-related
memos
=
tc
.
ListWithFilter
(
`!tags.exists(t, t.endsWith("bug"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 non-bug memo"
)
}
func
TestMemoFilterTagsExistsCombinedWithOtherFilters
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create memos with tags and other properties
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-archived-old"
,
tc
.
User
.
ID
)
.
Content
(
"Old archived memo"
)
.
Tags
(
"archive/old"
,
"done"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-archived-recent"
,
tc
.
User
.
ID
)
.
Content
(
"Recent archived memo with TODO"
)
.
Tags
(
"archive/recent"
,
"done"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-active-todo"
,
tc
.
User
.
ID
)
.
Content
(
"Active TODO"
)
.
Tags
(
"project/active"
,
"todo"
))
// Test: Combine tag filter with content filter
memos
:=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive")) && content.contains("TODO")`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 archived memo with TODO in content"
)
// Test: OR condition with tag filters
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive")) || tags.exists(t, t.contains("todo"))`
)
require
.
Len
(
t
,
memos
,
3
,
"Should find all memos (archived or with todo tag)"
)
// Test: Complex filter - archived but not containing "Recent"
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive")) && !content.contains("Recent")`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find 1 old archived memo"
)
}
func
TestMemoFilterTagsExistsEmptyAndNullCases
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create memo with no tags
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-no-tags"
,
tc
.
User
.
ID
)
.
Content
(
"Memo without tags"
))
// Create memo with tags
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-with-tags"
,
tc
.
User
.
ID
)
.
Content
(
"Memo with tags"
)
.
Tags
(
"tag1"
,
"tag2"
))
// Test: tags.exists should not match memos without tags
memos
:=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("tag"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should only find memo with tags"
)
// Test: Negation should match memos without matching tags
memos
=
tc
.
ListWithFilter
(
`!tags.exists(t, t.startsWith("tag"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find memo without matching tags"
)
}
// =============================================================================
// Issue #5480 - Real-world use case test
// =============================================================================
func
TestMemoFilterIssue5480_ArchiveWorkflow
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tc
:=
NewMemoFilterTestContext
(
t
)
defer
tc
.
Close
()
// Create a realistic scenario as described in issue #5480
// User has hierarchical tags and archives memos by prefixing with "archive"
// Active memos
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-homelab"
,
tc
.
User
.
ID
)
.
Content
(
"Setting up Memos"
)
.
Tags
(
"homelab/memos"
,
"tech"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-project-alpha"
,
tc
.
User
.
ID
)
.
Content
(
"Project Alpha notes"
)
.
Tags
(
"work/project-alpha"
,
"active"
))
// Archived memos (user prefixed tags with "archive")
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-old-homelab"
,
tc
.
User
.
ID
)
.
Content
(
"Old homelab setup"
)
.
Tags
(
"archive/homelab/old-server"
,
"done"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-old-project"
,
tc
.
User
.
ID
)
.
Content
(
"Old project beta"
)
.
Tags
(
"archive/work/project-beta"
,
"completed"
))
tc
.
CreateMemo
(
NewMemoBuilder
(
"memo-archived-personal"
,
tc
.
User
.
ID
)
.
Content
(
"Archived personal note"
)
.
Tags
(
"archive/personal/2024"
,
"old"
))
// Test: Filter out ALL archived memos using startsWith
memos
:=
tc
.
ListWithFilter
(
`!tags.exists(t, t.startsWith("archive"))`
)
require
.
Len
(
t
,
memos
,
2
,
"Should only show active memos (not archived)"
)
for
_
,
memo
:=
range
memos
{
for
_
,
tag
:=
range
memo
.
Payload
.
Tags
{
require
.
NotContains
(
t
,
tag
,
"archive"
,
"Active memos should not have archive prefix"
)
}
}
// Test: Show ONLY archived memos
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive"))`
)
require
.
Len
(
t
,
memos
,
3
,
"Should find all archived memos"
)
for
_
,
memo
:=
range
memos
{
hasArchiveTag
:=
false
for
_
,
tag
:=
range
memo
.
Payload
.
Tags
{
if
len
(
tag
)
>=
7
&&
tag
[
:
7
]
==
"archive"
{
hasArchiveTag
=
true
break
}
}
require
.
True
(
t
,
hasArchiveTag
,
"All returned memos should have archive prefix"
)
}
// Test: Filter archived homelab memos specifically
memos
=
tc
.
ListWithFilter
(
`tags.exists(t, t.startsWith("archive/homelab"))`
)
require
.
Len
(
t
,
memos
,
1
,
"Should find only archived homelab memos"
)
}
store/test/memo_filter_test.go
View file @
af2a2588
This diff is collapsed.
Click to expand it.
store/test/user_setting_test.go
View file @
af2a2588
...
...
@@ -2,6 +2,7 @@ package test
import
(
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
...
...
@@ -655,3 +656,220 @@ func TestUserSettingMultipleSettingTypes(t *testing.T) {
ts
.
Close
()
}
func
TestUserSettingShortcutsEdgeCases
(
t
*
testing
.
T
)
{
t
.
Parallel
()
ctx
:=
context
.
Background
()
ts
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
ts
)
require
.
NoError
(
t
,
err
)
// Case 1: Special characters in Filter and Title
// Includes quotes, backslashes, newlines, and other JSON-sensitive characters
specialCharsFilter
:=
`tag in ["work", "project"] && content.contains("urgent")`
specialCharsTitle
:=
`Work "Urgent" \ Notes`
shortcuts
:=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{
{
Id
:
"s1"
,
Title
:
specialCharsTitle
,
Filter
:
specialCharsFilter
},
},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
shortcuts
},
})
require
.
NoError
(
t
,
err
)
setting
,
err
:=
ts
.
GetUserSetting
(
ctx
,
&
store
.
FindUserSetting
{
UserID
:
&
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
setting
)
require
.
Len
(
t
,
setting
.
GetShortcuts
()
.
Shortcuts
,
1
)
require
.
Equal
(
t
,
specialCharsTitle
,
setting
.
GetShortcuts
()
.
Shortcuts
[
0
]
.
Title
)
require
.
Equal
(
t
,
specialCharsFilter
,
setting
.
GetShortcuts
()
.
Shortcuts
[
0
]
.
Filter
)
// Case 2: Unicode characters
unicodeFilter
:=
`tag in ["你好", "世界"]`
unicodeTitle
:=
`My 🚀 Shortcuts`
shortcuts
=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{
{
Id
:
"s2"
,
Title
:
unicodeTitle
,
Filter
:
unicodeFilter
},
},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
shortcuts
},
})
require
.
NoError
(
t
,
err
)
setting
,
err
=
ts
.
GetUserSetting
(
ctx
,
&
store
.
FindUserSetting
{
UserID
:
&
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
setting
)
require
.
Len
(
t
,
setting
.
GetShortcuts
()
.
Shortcuts
,
1
)
require
.
Equal
(
t
,
unicodeTitle
,
setting
.
GetShortcuts
()
.
Shortcuts
[
0
]
.
Title
)
require
.
Equal
(
t
,
unicodeFilter
,
setting
.
GetShortcuts
()
.
Shortcuts
[
0
]
.
Filter
)
// Case 3: Empty shortcuts list
// Should allow saving an empty list (clearing shortcuts)
shortcuts
=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
shortcuts
},
})
require
.
NoError
(
t
,
err
)
setting
,
err
=
ts
.
GetUserSetting
(
ctx
,
&
store
.
FindUserSetting
{
UserID
:
&
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
setting
)
require
.
NotNil
(
t
,
setting
.
GetShortcuts
())
require
.
Len
(
t
,
setting
.
GetShortcuts
()
.
Shortcuts
,
0
)
// Case 4: Large filter string
// Test reasonable large string handling (e.g. 4KB)
largeFilter
:=
strings
.
Repeat
(
"tag:long_tag_name "
,
200
)
shortcuts
=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{
{
Id
:
"s3"
,
Title
:
"Large Filter"
,
Filter
:
largeFilter
},
},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
shortcuts
},
})
require
.
NoError
(
t
,
err
)
setting
,
err
=
ts
.
GetUserSetting
(
ctx
,
&
store
.
FindUserSetting
{
UserID
:
&
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
setting
)
require
.
Equal
(
t
,
largeFilter
,
setting
.
GetShortcuts
()
.
Shortcuts
[
0
]
.
Filter
)
ts
.
Close
()
}
func
TestUserSettingShortcutsPartialUpdate
(
t
*
testing
.
T
)
{
t
.
Parallel
()
ctx
:=
context
.
Background
()
ts
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
ts
)
require
.
NoError
(
t
,
err
)
// Initial set
shortcuts
:=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{
{
Id
:
"s1"
,
Title
:
"Note 1"
,
Filter
:
"tag:1"
},
{
Id
:
"s2"
,
Title
:
"Note 2"
,
Filter
:
"tag:2"
},
},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
shortcuts
},
})
require
.
NoError
(
t
,
err
)
// Update by replacing the whole list (Store Upsert replaces the value for the key)
// We want to verify that we can "update" a single item by sending the modified list
updatedShortcuts
:=
&
storepb
.
ShortcutsUserSetting
{
Shortcuts
:
[]
*
storepb
.
ShortcutsUserSetting_Shortcut
{
{
Id
:
"s1"
,
Title
:
"Note 1 Updated"
,
Filter
:
"tag:1_updated"
},
{
Id
:
"s2"
,
Title
:
"Note 2"
,
Filter
:
"tag:2"
},
{
Id
:
"s3"
,
Title
:
"Note 3"
,
Filter
:
"tag:3"
},
// Add new one
},
}
_
,
err
=
ts
.
UpsertUserSetting
(
ctx
,
&
storepb
.
UserSetting
{
UserId
:
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
Value
:
&
storepb
.
UserSetting_Shortcuts
{
Shortcuts
:
updatedShortcuts
},
})
require
.
NoError
(
t
,
err
)
setting
,
err
:=
ts
.
GetUserSetting
(
ctx
,
&
store
.
FindUserSetting
{
UserID
:
&
user
.
ID
,
Key
:
storepb
.
UserSetting_SHORTCUTS
,
})
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
setting
)
require
.
Len
(
t
,
setting
.
GetShortcuts
()
.
Shortcuts
,
3
)
// Verify updates
for
_
,
s
:=
range
setting
.
GetShortcuts
()
.
Shortcuts
{
if
s
.
Id
==
"s1"
{
require
.
Equal
(
t
,
"Note 1 Updated"
,
s
.
Title
)
require
.
Equal
(
t
,
"tag:1_updated"
,
s
.
Filter
)
}
else
if
s
.
Id
==
"s2"
{
require
.
Equal
(
t
,
"Note 2"
,
s
.
Title
)
}
else
if
s
.
Id
==
"s3"
{
require
.
Equal
(
t
,
"Note 3"
,
s
.
Title
)
}
}
ts
.
Close
()
}
func
TestUserSettingJSONFieldsEdgeCases
(
t
*
testing
.
T
)
{
t
.
Parallel
()
ctx
:=
context
.
Background
()
ts
:=
NewTestingStore
(
ctx
,
t
)
user
,
err
:=
createTestingHostUser
(
ctx
,
ts
)
require
.
NoError
(
t
,
err
)
// Case 1: Webhook with special characters and Unicode in Title and URL
specialWebhook
:=
&
storepb
.
WebhooksUserSetting_Webhook
{
Id
:
"wh-special"
,
Title
:
`My "Special" & <Webhook> 🚀`
,
Url
:
"https://example.com/hook?query=你好¶m=
\"
value
\"
"
,
}
err
=
ts
.
AddUserWebhook
(
ctx
,
user
.
ID
,
specialWebhook
)
require
.
NoError
(
t
,
err
)
webhooks
,
err
:=
ts
.
GetUserWebhooks
(
ctx
,
user
.
ID
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
webhooks
,
1
)
require
.
Equal
(
t
,
specialWebhook
.
Title
,
webhooks
[
0
]
.
Title
)
require
.
Equal
(
t
,
specialWebhook
.
Url
,
webhooks
[
0
]
.
Url
)
// Case 2: PAT with special description
specialPAT
:=
&
storepb
.
PersonalAccessTokensUserSetting_PersonalAccessToken
{
TokenId
:
"pat-special"
,
TokenHash
:
"hash-special"
,
Description
:
"Token for 'CLI'
\n
&
\"
API
\"
\t
with unicode 🔑"
,
}
err
=
ts
.
AddUserPersonalAccessToken
(
ctx
,
user
.
ID
,
specialPAT
)
require
.
NoError
(
t
,
err
)
pats
,
err
:=
ts
.
GetUserPersonalAccessTokens
(
ctx
,
user
.
ID
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
pats
,
1
)
require
.
Equal
(
t
,
specialPAT
.
Description
,
pats
[
0
]
.
Description
)
// Case 3: Refresh Token with special description
specialRefreshToken
:=
&
storepb
.
RefreshTokensUserSetting_RefreshToken
{
TokenId
:
"rt-special"
,
Description
:
"Browser: Firefox (Nightly) / OS: Linux 🐧"
,
}
err
=
ts
.
AddUserRefreshToken
(
ctx
,
user
.
ID
,
specialRefreshToken
)
require
.
NoError
(
t
,
err
)
tokens
,
err
:=
ts
.
GetUserRefreshTokens
(
ctx
,
user
.
ID
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
tokens
,
1
)
require
.
Equal
(
t
,
specialRefreshToken
.
Description
,
tokens
[
0
]
.
Description
)
ts
.
Close
()
}
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