@@ -638,3 +638,190 @@ func TestVFSFile_WriteBufferClearAfterSync(t *testing.T) {
638638 t .Errorf ("buffer should be empty after sync, got size %d" , stat .Size ())
639639 }
640640}
641+
642+ func TestVFSFile_OpenNewDatabase (t * testing.T ) {
643+ // Test opening a VFSFile with write mode enabled when no LTX files exist (new database)
644+ client := newWriteTestReplicaClient ()
645+ // Note: No LTX files created - simulating a brand new database
646+
647+ // Create temp directory for buffer
648+ tmpDir := t .TempDir ()
649+ bufferPath := tmpDir + "/.litestream-write-buffer"
650+
651+ // Create VFSFile with write support - no existing data
652+ logger := slog .Default ()
653+ f := NewVFSFile (client , "new.db" , logger )
654+ f .writeEnabled = true
655+ f .dirty = make (map [uint32 ]int64 )
656+ f .syncInterval = 0
657+ f .bufferPath = bufferPath
658+
659+ if err := f .Open (); err != nil {
660+ t .Fatal (err )
661+ }
662+ defer f .Close ()
663+
664+ // Verify it opened successfully as a new database
665+ if f .pageSize != DefaultPageSize {
666+ t .Errorf ("expected page size %d, got %d" , DefaultPageSize , f .pageSize )
667+ }
668+
669+ if f .pos .TXID != 0 {
670+ t .Errorf ("expected TXID 0 for new database, got %d" , f .pos .TXID )
671+ }
672+
673+ if f .expectedTXID != 0 {
674+ t .Errorf ("expected expectedTXID 0, got %d" , f .expectedTXID )
675+ }
676+
677+ if f .pendingTXID != 1 {
678+ t .Errorf ("expected pendingTXID 1, got %d" , f .pendingTXID )
679+ }
680+
681+ if f .commit != 0 {
682+ t .Errorf ("expected commit 0 for new database, got %d" , f .commit )
683+ }
684+ }
685+
686+ func TestVFSFile_NewDatabase_ReadReturnsZeros (t * testing.T ) {
687+ // Test that reading from a new database returns zeros
688+ client := newWriteTestReplicaClient ()
689+
690+ tmpDir := t .TempDir ()
691+ bufferPath := tmpDir + "/.litestream-write-buffer"
692+
693+ logger := slog .Default ()
694+ f := NewVFSFile (client , "new.db" , logger )
695+ f .writeEnabled = true
696+ f .dirty = make (map [uint32 ]int64 )
697+ f .syncInterval = 0
698+ f .bufferPath = bufferPath
699+
700+ if err := f .Open (); err != nil {
701+ t .Fatal (err )
702+ }
703+ defer f .Close ()
704+
705+ // Read page 1 - should return zeros for new database
706+ readBuf := make ([]byte , 100 )
707+ n , err := f .ReadAt (readBuf , 0 )
708+ if err != nil {
709+ t .Fatalf ("expected no error reading from new database, got: %v" , err )
710+ }
711+ if n != len (readBuf ) {
712+ t .Errorf ("expected %d bytes, got %d" , len (readBuf ), n )
713+ }
714+
715+ // Verify all zeros
716+ for i , b := range readBuf {
717+ if b != 0 {
718+ t .Errorf ("expected zero at position %d, got %d" , i , b )
719+ break
720+ }
721+ }
722+ }
723+
724+ func TestVFSFile_NewDatabase_WriteAndSync (t * testing.T ) {
725+ // Test writing to a new database and syncing to remote
726+ client := newWriteTestReplicaClient ()
727+
728+ tmpDir := t .TempDir ()
729+ bufferPath := tmpDir + "/.litestream-write-buffer"
730+
731+ logger := slog .Default ()
732+ f := NewVFSFile (client , "new.db" , logger )
733+ f .writeEnabled = true
734+ f .dirty = make (map [uint32 ]int64 )
735+ f .syncInterval = 0
736+ f .bufferPath = bufferPath
737+
738+ if err := f .Open (); err != nil {
739+ t .Fatal (err )
740+ }
741+ defer f .Close ()
742+
743+ // Write data to page 1
744+ writeData := []byte ("new database content" )
745+ n , err := f .WriteAt (writeData , 0 )
746+ if err != nil {
747+ t .Fatal (err )
748+ }
749+ if n != len (writeData ) {
750+ t .Errorf ("expected %d bytes written, got %d" , len (writeData ), n )
751+ }
752+
753+ // Verify dirty page exists
754+ if len (f .dirty ) != 1 {
755+ t .Errorf ("expected 1 dirty page, got %d" , len (f .dirty ))
756+ }
757+
758+ // Sync to remote
759+ if err := f .Sync (0 ); err != nil {
760+ t .Fatal (err )
761+ }
762+
763+ // Verify TXID advanced
764+ if f .expectedTXID != 1 {
765+ t .Errorf ("expected expectedTXID 1 after sync, got %d" , f .expectedTXID )
766+ }
767+ if f .pendingTXID != 2 {
768+ t .Errorf ("expected pendingTXID 2 after sync, got %d" , f .pendingTXID )
769+ }
770+
771+ // Verify LTX file was written
772+ client .mu .Lock ()
773+ if len (client .ltxFiles [0 ]) != 1 {
774+ t .Errorf ("expected 1 LTX file after sync, got %d" , len (client .ltxFiles [0 ]))
775+ }
776+ if len (client .ltxFiles [0 ]) > 0 {
777+ info := client .ltxFiles [0 ][0 ]
778+ if info .MinTXID != 1 || info .MaxTXID != 1 {
779+ t .Errorf ("expected TXID 1, got min=%d max=%d" , info .MinTXID , info .MaxTXID )
780+ }
781+ }
782+ client .mu .Unlock ()
783+ }
784+
785+ func TestVFSFile_NewDatabase_FileSize (t * testing.T ) {
786+ // Test that FileSize returns 0 for a new empty database
787+ client := newWriteTestReplicaClient ()
788+
789+ tmpDir := t .TempDir ()
790+ bufferPath := tmpDir + "/.litestream-write-buffer"
791+
792+ logger := slog .Default ()
793+ f := NewVFSFile (client , "new.db" , logger )
794+ f .writeEnabled = true
795+ f .dirty = make (map [uint32 ]int64 )
796+ f .syncInterval = 0
797+ f .bufferPath = bufferPath
798+
799+ if err := f .Open (); err != nil {
800+ t .Fatal (err )
801+ }
802+ defer f .Close ()
803+
804+ // FileSize should be 0 for empty database
805+ size , err := f .FileSize ()
806+ if err != nil {
807+ t .Fatal (err )
808+ }
809+ if size != 0 {
810+ t .Errorf ("expected size 0 for new database, got %d" , size )
811+ }
812+
813+ // Write a page
814+ data := make ([]byte , DefaultPageSize )
815+ if _ , err := f .WriteAt (data , 0 ); err != nil {
816+ t .Fatal (err )
817+ }
818+
819+ // FileSize should now reflect the dirty page
820+ size , err = f .FileSize ()
821+ if err != nil {
822+ t .Fatal (err )
823+ }
824+ if size != int64 (DefaultPageSize ) {
825+ t .Errorf ("expected size %d after write, got %d" , DefaultPageSize , size )
826+ }
827+ }
0 commit comments