11package zfs
22
33import (
4+ "encoding/base64"
45 "errors"
56 "sort"
67 "testing"
@@ -9,6 +10,8 @@ import (
910 "github.com/stretchr/testify/require"
1011
1112 "gitlab.com/postgres-ai/database-lab/v3/internal/provision/resources"
13+ "gitlab.com/postgres-ai/database-lab/v3/internal/provision/thinclones"
14+ "gitlab.com/postgres-ai/database-lab/v3/pkg/models"
1215)
1316
1417type runnerMock struct {
@@ -352,3 +355,205 @@ test_pool/other/dataset -`,
352355 })
353356 }
354357}
358+
359+ func TestListAllBranches (t * testing.T ) {
360+ testCases := []struct {
361+ name string
362+ output string
363+ expected []models.BranchEntity
364+ }{
365+ {
366+ name : "empty output" ,
367+ output : "" ,
368+ expected : []models.BranchEntity {},
369+ },
370+ {
371+ name : "tab-delimited output with space-containing branch name" ,
372+ output : "my branch\t pool/branch/main@snap1" ,
373+ expected : []models.BranchEntity {{Name : "my branch" , Dataset : "pool" , SnapshotID : "pool/branch/main@snap1" }},
374+ },
375+ {
376+ name : "multiple branches from comma-separated field" ,
377+ output : "br1,br2\t pool@snap1" ,
378+ expected : []models.BranchEntity {{Name : "br1" , Dataset : "pool" , SnapshotID : "pool@snap1" }, {Name : "br2" , Dataset : "pool" , SnapshotID : "pool@snap1" }},
379+ },
380+ {
381+ name : "line with wrong column count is skipped" ,
382+ output : "single_field_no_tab\n branch1\t pool@snap2" ,
383+ expected : []models.BranchEntity {{Name : "branch1" , Dataset : "pool" , SnapshotID : "pool@snap2" }},
384+ },
385+ {
386+ name : "multiple lines with tabs" ,
387+ output : "main\t pool@snap1\n feature\t pool@snap2" ,
388+ expected : []models.BranchEntity {
389+ {Name : "main" , Dataset : "pool" , SnapshotID : "pool@snap1" },
390+ {Name : "feature" , Dataset : "pool" , SnapshotID : "pool@snap2" },
391+ },
392+ },
393+ }
394+
395+ for _ , tc := range testCases {
396+ t .Run (tc .name , func (t * testing.T ) {
397+ m := Manager {runner : runnerMock {cmdOutput : tc .output }, config : Config {Pool : resources .NewPool ("pool" )}}
398+
399+ branches , err := m .ListAllBranches (nil )
400+ require .NoError (t , err )
401+ assert .Equal (t , tc .expected , branches )
402+ })
403+ }
404+ }
405+
406+ func TestListBranches (t * testing.T ) {
407+ testCases := []struct {
408+ name string
409+ output string
410+ expected map [string ]string
411+ }{
412+ {
413+ name : "empty output" ,
414+ output : "" ,
415+ expected : map [string ]string {},
416+ },
417+ {
418+ name : "normal two-column tab-delimited output" ,
419+ output : "main\t pool@snap1\n feature\t pool@snap2" ,
420+ expected : map [string ]string {"main" : "pool@snap1" , "feature" : "pool@snap2" },
421+ },
422+ {
423+ name : "snapshot name containing spaces is preserved" ,
424+ output : "main\t pool@snap with spaces" ,
425+ expected : map [string ]string {"main" : "pool@snap with spaces" },
426+ },
427+ {
428+ name : "single-field line without tab is skipped" ,
429+ output : "no_tab_here\n main\t pool@snap1" ,
430+ expected : map [string ]string {"main" : "pool@snap1" },
431+ },
432+ {
433+ name : "comma-separated branches map to same snapshot" ,
434+ output : "br1,br2\t pool@snap1" ,
435+ expected : map [string ]string {"br1" : "pool@snap1" , "br2" : "pool@snap1" },
436+ },
437+ }
438+
439+ for _ , tc := range testCases {
440+ t .Run (tc .name , func (t * testing.T ) {
441+ m := Manager {runner : runnerMock {cmdOutput : tc .output }, config : Config {Pool : resources .NewPool ("pool" )}}
442+
443+ branches , err := m .listBranches ()
444+ require .NoError (t , err )
445+ assert .Equal (t , tc .expected , branches )
446+ })
447+ }
448+ }
449+
450+ func TestGetRepo (t * testing.T ) {
451+ msg := base64 .StdEncoding .EncodeToString ([]byte ("initial commit" ))
452+
453+ testCases := []struct {
454+ name string
455+ output string
456+ wantSnapshots int
457+ wantBranches map [string ]string
458+ }{
459+ {
460+ name : "empty output" ,
461+ output : "" ,
462+ wantSnapshots : 0 ,
463+ wantBranches : map [string ]string {},
464+ },
465+ {
466+ name : "all 8 fields populated" ,
467+ output : "pool@snap1\t parent1\t child1\t main\t root1\t 20250101\t " + msg + "\t -" ,
468+ wantSnapshots : 1 ,
469+ wantBranches : map [string ]string {"main" : "pool@snap1" },
470+ },
471+ {
472+ name : "line with fewer than 8 fields is skipped" ,
473+ output : "pool@snap1\t parent1\t child1\t main\t root1\t 20250101\t " + msg ,
474+ wantSnapshots : 0 ,
475+ wantBranches : map [string ]string {},
476+ },
477+ {
478+ name : "base64 commit message decodes correctly" ,
479+ output : "pool@snap1\t -\t -\t main\t -\t 20250101\t " + msg + "\t -" ,
480+ wantSnapshots : 1 ,
481+ wantBranches : map [string ]string {"main" : "pool@snap1" },
482+ },
483+ {
484+ name : "multiple snapshots with branches" ,
485+ output : "pool@snap1\t -\t pool@snap2\t main\t root1\t 20250101\t " + msg + "\t -\n " +
486+ "pool@snap2\t pool@snap1\t -\t feature\t root1\t 20250102\t " + msg + "\t -" ,
487+ wantSnapshots : 2 ,
488+ wantBranches : map [string ]string {"main" : "pool@snap1" , "feature" : "pool@snap2" },
489+ },
490+ }
491+
492+ for _ , tc := range testCases {
493+ t .Run (tc .name , func (t * testing.T ) {
494+ m := Manager {runner : runnerMock {cmdOutput : tc .output }, config : Config {Pool : resources .NewPool ("pool" )}}
495+
496+ repo , err := m .getRepo (cmdCfg {pool : "pool" })
497+ require .NoError (t , err )
498+ assert .Len (t , repo .Snapshots , tc .wantSnapshots )
499+ assert .Equal (t , tc .wantBranches , repo .Branches )
500+
501+ if tc .wantSnapshots > 0 {
502+ for _ , snap := range repo .Snapshots {
503+ if snap .Message != "" && snap .Message != "-" {
504+ assert .Equal (t , "initial commit" , snap .Message )
505+ }
506+ }
507+ }
508+ })
509+ }
510+ }
511+
512+ func TestGetSnapshotProperties (t * testing.T ) {
513+ msg := base64 .StdEncoding .EncodeToString ([]byte ("test message" ))
514+
515+ testCases := []struct {
516+ name string
517+ output string
518+ expected thinclones.SnapshotProperties
519+ }{
520+ {
521+ name : "well-formed 8-field output" ,
522+ output : "pool@snap1\t parent1\t child1\t main\t root1\t 20250101\t " + msg + "\t clone1" ,
523+ expected : thinclones.SnapshotProperties {
524+ Name : "pool@snap1" , Parent : "parent1" , Child : "child1" , Branch : "main" ,
525+ Root : "root1" , DataStateAt : "20250101" , Message : "test message" , Clones : "clone1" ,
526+ },
527+ },
528+ {
529+ name : "field containing spaces is preserved" ,
530+ output : "pool@snap1\t parent with spaces\t child1\t main\t root1\t 20250101\t " + msg + "\t clone1" ,
531+ expected : thinclones.SnapshotProperties {
532+ Name : "pool@snap1" , Parent : "parent with spaces" , Child : "child1" , Branch : "main" ,
533+ Root : "root1" , DataStateAt : "20250101" , Message : "test message" , Clones : "clone1" ,
534+ },
535+ },
536+ }
537+
538+ for _ , tc := range testCases {
539+ t .Run (tc .name , func (t * testing.T ) {
540+ m := Manager {runner : runnerMock {cmdOutput : tc .output }, config : Config {Pool : resources .NewPool ("pool" )}}
541+
542+ props , err := m .GetSnapshotProperties ("pool@snap1" )
543+ require .NoError (t , err )
544+ assert .Equal (t , tc .expected , props )
545+ })
546+ }
547+ }
548+
549+ func TestGetSnapshotPropertiesMalformed (t * testing.T ) {
550+ msg := base64 .StdEncoding .EncodeToString ([]byte ("test message" ))
551+
552+ m := Manager {
553+ runner : runnerMock {cmdOutput : "pool@snap1\t parent1\t child1\t main\t root1\t 20250101\t " + msg },
554+ config : Config {Pool : resources .NewPool ("pool" )},
555+ }
556+
557+ _ , err := m .GetSnapshotProperties ("pool@snap1" )
558+ assert .Error (t , err )
559+ }
0 commit comments