@@ -30,18 +30,10 @@ const (
3030
3131 // Memos container settings for migration testing.
3232 MemosDockerImage = "neosmemo/memos"
33- StableMemosVersion = "stable"
33+ StableMemosVersion = "stable" // Always points to the latest stable release
3434)
3535
3636var (
37- // MemosStartupWaitStrategy defines the wait strategy for Memos container startup.
38- // It waits for the "started" log message (compatible with both old and new versions)
39- // and checks if port 5230 is listening.
40- MemosStartupWaitStrategy = wait .ForAll (
41- wait .ForLog ("started" ),
42- wait .ForListeningPort ("5230/tcp" ),
43- ).WithDeadline (180 * time .Second )
44-
4537 mysqlContainer atomic.Pointer [mysql.MySQLContainer ]
4638 postgresContainer atomic.Pointer [postgres.PostgresContainer ]
4739 mysqlOnce sync.Once
@@ -235,105 +227,6 @@ func GetPostgresDSN(t *testing.T) string {
235227 return strings .Replace (dsn , "/init_db?" , "/" + dbName + "?" , 1 )
236228}
237229
238- // GetDedicatedMySQLDSN starts a dedicated MySQL container for migration testing.
239- // This is needed because older Memos versions have bugs when connecting to a MySQL
240- // server that has other initialized databases (they incorrectly query migration_history
241- // on a fresh database without checking if the DB is initialized).
242- // Returns: DSN for host access, container hostname for internal network access, cleanup function.
243- func GetDedicatedMySQLDSN (t * testing.T ) (dsn string , containerHost string , cleanup func ()) {
244- ctx := context .Background ()
245-
246- nw , err := getTestNetwork (ctx )
247- if err != nil {
248- t .Fatalf ("failed to create test network: %v" , err )
249- }
250-
251- container , err := mysql .Run (ctx ,
252- "mysql:8" ,
253- mysql .WithDatabase ("memos" ),
254- mysql .WithUsername ("root" ),
255- mysql .WithPassword (testPassword ),
256- testcontainers .WithEnv (map [string ]string {
257- "MYSQL_ROOT_PASSWORD" : testPassword ,
258- }),
259- testcontainers .WithWaitStrategy (
260- wait .ForAll (
261- wait .ForLog ("ready for connections" ).WithOccurrence (2 ),
262- wait .ForListeningPort ("3306/tcp" ),
263- ).WithDeadline (120 * time .Second ),
264- ),
265- network .WithNetwork (nil , nw ),
266- )
267- if err != nil {
268- t .Fatalf ("failed to start dedicated MySQL container: %v" , err )
269- }
270-
271- hostDSN , err := container .ConnectionString (ctx , "multiStatements=true" )
272- if err != nil {
273- container .Terminate (ctx )
274- t .Fatalf ("failed to get MySQL connection string: %v" , err )
275- }
276-
277- if err := waitForDB ("mysql" , hostDSN , 30 * time .Second ); err != nil {
278- container .Terminate (ctx )
279- t .Fatalf ("MySQL not ready for connections: %v" , err )
280- }
281-
282- name , _ := container .Name (ctx )
283- host := strings .TrimPrefix (name , "/" )
284-
285- return hostDSN , host , func () {
286- container .Terminate (ctx )
287- }
288- }
289-
290- // GetDedicatedPostgresDSN starts a dedicated PostgreSQL container for migration testing.
291- // This is needed for isolation when testing migrations with older Memos versions.
292- // Returns: DSN for host access, container hostname for internal network access, cleanup function.
293- func GetDedicatedPostgresDSN (t * testing.T ) (dsn string , containerHost string , cleanup func ()) {
294- ctx := context .Background ()
295-
296- nw , err := getTestNetwork (ctx )
297- if err != nil {
298- t .Fatalf ("failed to create test network: %v" , err )
299- }
300-
301- container , err := postgres .Run (ctx ,
302- "postgres:18" ,
303- postgres .WithDatabase ("memos" ),
304- postgres .WithUsername (testUser ),
305- postgres .WithPassword (testPassword ),
306- testcontainers .WithWaitStrategy (
307- wait .ForAll (
308- wait .ForLog ("database system is ready to accept connections" ).WithOccurrence (2 ),
309- wait .ForListeningPort ("5432/tcp" ),
310- ).WithDeadline (120 * time .Second ),
311- ),
312- network .WithNetwork (nil , nw ),
313- )
314- if err != nil {
315- t .Fatalf ("failed to start dedicated PostgreSQL container: %v" , err )
316- }
317-
318- hostDSN , err := container .ConnectionString (ctx , "sslmode=disable" )
319- if err != nil {
320- container .Terminate (ctx )
321- t .Fatalf ("failed to get PostgreSQL connection string: %v" , err )
322- }
323-
324- if err := waitForDB ("postgres" , hostDSN , 30 * time .Second ); err != nil {
325- container .Terminate (ctx )
326- t .Fatalf ("PostgreSQL not ready for connections: %v" , err )
327- }
328-
329- name , _ := container .Name (ctx )
330- host := strings .TrimPrefix (name , "/" )
331-
332- return hostDSN , host , func () {
333- container .Terminate (ctx )
334- }
335- }
336-
337230// TerminateContainers cleans up all running containers and network.
338231// This is typically called from TestMain.
339232func TerminateContainers () {
@@ -349,45 +242,28 @@ func TerminateContainers() {
349242 }
350243}
351244
352- // GetMySQLContainerHost returns the MySQL container hostname for use within the Docker network.
353- func GetMySQLContainerHost () string {
354- container := mysqlContainer .Load ()
355- if container == nil {
356- return ""
357- }
358- name , _ := container .Name (context .Background ())
359- // Remove leading slash from container name
360- return strings .TrimPrefix (name , "/" )
361- }
362-
363- // GetPostgresContainerHost returns the PostgreSQL container hostname for use within the Docker network.
364- func GetPostgresContainerHost () string {
365- container := postgresContainer .Load ()
366- if container == nil {
367- return ""
368- }
369- name , _ := container .Name (context .Background ())
370- return strings .TrimPrefix (name , "/" )
371- }
372-
373245// MemosContainerConfig holds configuration for starting a Memos container.
374246type MemosContainerConfig struct {
375- Version string // Memos version tag (e.g., "0.25 ")
247+ Version string // Memos version tag (e.g., "0.24.0 ")
376248 Driver string // Database driver: sqlite, mysql, postgres
377249 DSN string // Database DSN (for mysql/postgres)
378250 DataDir string // Host directory to mount for SQLite data
379251}
380252
253+ // MemosStartupWaitStrategy defines the wait strategy for Memos container startup.
254+ // Uses regex to match various log message formats across versions.
255+ var MemosStartupWaitStrategy = wait .ForAll (
256+ wait .ForLog ("(started successfully|has been started on port)" ).AsRegexp (),
257+ wait .ForListeningPort ("5230/tcp" ),
258+ ).WithDeadline (180 * time .Second )
259+
381260// StartMemosContainer starts a Memos container for migration testing.
382261// For SQLite, it mounts the dataDir to /var/opt/memos.
383- // For MySQL/PostgreSQL, it connects to the provided DSN via the test network.
384- // If Version is "local", builds the image from the local Dockerfile.
385262func StartMemosContainer (ctx context.Context , cfg MemosContainerConfig ) (testcontainers.Container , error ) {
386263 env := map [string ]string {
387264 "MEMOS_MODE" : "prod" ,
388265 }
389266
390- var mounts []testcontainers.ContainerMount
391267 var opts []testcontainers.ContainerCustomizer
392268
393269 switch cfg .Driver {
@@ -396,55 +272,46 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon
396272 opts = append (opts , testcontainers .WithHostConfigModifier (func (hc * container.HostConfig ) {
397273 hc .Binds = append (hc .Binds , fmt .Sprintf ("%s:%s" , cfg .DataDir , "/var/opt/memos" ))
398274 }))
399- case "mysql" :
400- env ["MEMOS_DRIVER" ] = "mysql"
401- env ["MEMOS_DSN" ] = cfg .DSN
402- opts = append (opts , network .WithNetwork (nil , testDockerNetwork .Load ()))
403- case "postgres" :
404- env ["MEMOS_DRIVER" ] = "postgres"
405- env ["MEMOS_DSN" ] = cfg .DSN
406- opts = append (opts , network .WithNetwork (nil , testDockerNetwork .Load ()))
407275 default :
408- return nil , errors .Errorf ("unsupported driver: %s" , cfg .Driver )
276+ return nil , errors .Errorf ("unsupported driver for migration testing : %s" , cfg .Driver )
409277 }
410278
411279 req := testcontainers.ContainerRequest {
280+ Image : fmt .Sprintf ("%s:%s" , MemosDockerImage , cfg .Version ),
412281 Env : env ,
413- Mounts : testcontainers .Mounts (mounts ... ),
414282 ExposedPorts : []string {"5230/tcp" },
415283 WaitingFor : MemosStartupWaitStrategy ,
284+ User : fmt .Sprintf ("%d:%d" , os .Getuid (), os .Getgid ()),
416285 }
417286
418- // Use local Dockerfile build or remote image
287+ // Use local image if specified
419288 if cfg .Version == "local" {
420289 if os .Getenv ("MEMOS_TEST_IMAGE_BUILT" ) == "1" {
421290 req .Image = "memos-test:local"
422291 } else {
423292 req .FromDockerfile = testcontainers.FromDockerfile {
424293 Context : "../../" ,
425- Dockerfile : "store/test/ Dockerfile" , // Simple Dockerfile without BuildKit requirements
294+ Dockerfile : "Dockerfile" ,
426295 }
427296 }
428- } else {
429- req .Image = fmt .Sprintf ("%s:%s" , MemosDockerImage , cfg .Version )
430297 }
431298
432299 genericReq := testcontainers.GenericContainerRequest {
433300 ContainerRequest : req ,
434301 Started : true ,
435302 }
436303
437- // Apply network options
304+ // Apply options
438305 for _ , opt := range opts {
439306 if err := opt .Customize (& genericReq ); err != nil {
440307 return nil , errors .Wrap (err , "failed to apply container option" )
441308 }
442309 }
443310
444- container , err := testcontainers .GenericContainer (ctx , genericReq )
311+ ctr , err := testcontainers .GenericContainer (ctx , genericReq )
445312 if err != nil {
446313 return nil , errors .Wrap (err , "failed to start memos container" )
447314 }
448315
449- return container , nil
316+ return ctr , nil
450317}
0 commit comments