1+ package curd
2+
3+ import (
4+ "strings"
5+ "sync"
6+ "testing"
7+
8+ "github.com/google/uuid"
9+ "github.com/langgenius/dify-plugin-daemon/internal/db"
10+ "github.com/langgenius/dify-plugin-daemon/internal/types/app"
11+ "github.com/langgenius/dify-plugin-daemon/internal/types/models"
12+ "github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
13+ "github.com/stretchr/testify/require"
14+ )
15+
16+ // TestInstallPlugin_IdempotentUnderConcurrency ensures creating the same plugin/installation
17+ // concurrently is idempotent: only one plugin and one installation row are persisted.
18+ func TestInstallPlugin_IdempotentUnderConcurrency (t * testing.T ) {
19+ cfg := & app.Config {
20+ DBType : app .DB_TYPE_POSTGRESQL ,
21+ DBUsername : "postgres" ,
22+ DBPassword : "difyai123456" ,
23+ DBHost : "localhost" ,
24+ DBPort : 5432 ,
25+ DBDatabase : "dify_plugin_daemon" ,
26+ DBSslMode : "disable" ,
27+ }
28+ cfg .SetDefault ()
29+ db .Init (cfg )
30+ t .Cleanup (db .Close )
31+
32+ tenantID := uuid .NewString ()
33+ pluginName := "concurrency_demo_" + uuid .NewString ()
34+ checksum := uuid .NewString ()
35+ checksum = strings .ReplaceAll (checksum , "-" , "" )
36+ // 32 hex chars
37+ if len (checksum ) > 32 {
38+ checksum = checksum [:32 ]
39+ }
40+
41+ identifier , err := plugin_entities .NewPluginUniqueIdentifier ("tester/" + pluginName + ":1.0.0.0@" + checksum )
42+ require .NoError (t , err )
43+
44+ const workers = 8
45+ var wg sync.WaitGroup
46+ wg .Add (workers )
47+ errs := make (chan error , workers )
48+ for i := 0 ; i < workers ; i ++ {
49+ go func () {
50+ defer wg .Done ()
51+ _ , _ , err := InstallPlugin (
52+ tenantID ,
53+ identifier ,
54+ plugin_entities .PLUGIN_RUNTIME_TYPE_LOCAL ,
55+ & plugin_entities.PluginDeclaration {},
56+ "unittest" ,
57+ map [string ]any {"from" : "test" },
58+ )
59+ errs <- err
60+ }()
61+ }
62+ wg .Wait ()
63+ close (errs )
64+
65+ // Validate DB state: exactly one plugin and one installation persisted
66+ plugins , err := db .GetAll [models.Plugin ](
67+ db .Equal ("plugin_unique_identifier" , identifier .String ()),
68+ db .Equal ("install_type" , string (plugin_entities .PLUGIN_RUNTIME_TYPE_LOCAL )),
69+ )
70+ require .NoError (t , err )
71+ require .Len (t , plugins , 1 , "should persist exactly one plugin record" )
72+
73+ installations , err := db .GetAll [models.PluginInstallation ](
74+ db .Equal ("plugin_unique_identifier" , identifier .String ()),
75+ db .Equal ("tenant_id" , tenantID ),
76+ )
77+ require .NoError (t , err )
78+ require .Len (t , installations , 1 , "should persist exactly one installation record for tenant" )
79+
80+ // A subsequent sequential install should be rejected as already installed
81+ _ , _ , err = InstallPlugin (
82+ tenantID ,
83+ identifier ,
84+ plugin_entities .PLUGIN_RUNTIME_TYPE_LOCAL ,
85+ & plugin_entities.PluginDeclaration {},
86+ "unittest" ,
87+ map [string ]any {"from" : "test" },
88+ )
89+ require .ErrorIs (t , err , ErrPluginAlreadyInstalled )
90+ }
0 commit comments