package mft import ( "bytes" "encoding/binary" "errors" "io" "testing" "unicode/utf16" ) func TestShouldPreferFileName(t *testing.T) { tests := []struct { current string candidate string want bool }{ {current: "", candidate: "LONGNAME.TXT", want: true}, {current: "PROGRA~1", candidate: "Program Files", want: true}, {current: "Program Files", candidate: "PROGRA~1", want: false}, {current: "abc", candidate: "abcdef", want: true}, {current: "abcdef", candidate: "abc", want: false}, } for _, tt := range tests { if got := shouldPreferFileName(tt.current, tt.candidate); got != tt.want { t.Fatalf("shouldPreferFileName(%q, %q) = %v, want %v", tt.current, tt.candidate, got, tt.want) } } } func TestShouldPreferFileNameWithNamespace(t *testing.T) { if !shouldPreferFileNameWithNamespace("PROGRA~1", FileNameNamespaceDos, "Program Files", FileNameNamespaceWin32) { t.Fatal("expected Win32 name to win over DOS name") } if shouldPreferFileNameWithNamespace("Program Files", FileNameNamespaceWin32, "PROGRA~1", FileNameNamespaceDos) { t.Fatal("did not expect DOS name to replace Win32 name") } } func TestFileFromRecordIncludesParent(t *testing.T) { parent := FileReference{RecordNumber: 42, SequenceNumber: 7}.ToUint64() record := Record{ FileReference: FileReference{RecordNumber: 100, SequenceNumber: 9}, Flags: RecordFlagInUse, Attributes: []Attribute{ {Type: AttributeTypeFileName, Data: testFileNameData("PROGRA~1", parent, FileNameNamespaceDos)}, {Type: AttributeTypeFileName, Data: testFileNameData("Program Files", parent, FileNameNamespaceWin32)}, {Type: AttributeTypeData, ActualSize: 12, AllocatedSize: 16}, }, } file, ok := FileFromRecord(record) if !ok { t.Fatal("expected file to be extracted") } if file.Name != "Program Files" { t.Fatalf("file.Name = %q, want %q", file.Name, "Program Files") } if file.Parent != parent { t.Fatalf("file.Parent = %d, want %d", file.Parent, parent) } if file.Node != record.FileReference.ToUint64() { t.Fatalf("file.Node = %d, want %d", file.Node, record.FileReference.ToUint64()) } if file.Size != 12 || file.Aszie != 16 { t.Fatalf("unexpected size fields: size=%d asize=%d", file.Size, file.Aszie) } } func TestCopyFilesReportsProgress(t *testing.T) { progress := make([]float64, 0) var dst testWriter reader := &testChunkReader{chunks: [][]byte{{'a', 'b'}, {'c', 'd'}}} written, err := copyFiles(&dst, reader, 4, func(_, _ int64, percent float64) { progress = append(progress, percent) }) if err != nil { t.Fatalf("copyFiles failed: %v", err) } if written != 4 { t.Fatalf("written = %d, want 4", written) } if len(progress) == 0 { t.Fatal("expected progress callbacks") } if progress[len(progress)-1] != 100 { t.Fatalf("final progress = %v, want 100", progress[len(progress)-1]) } } func TestWalkRecordsIgnoresPartialTail(t *testing.T) { reader := bytes.NewReader(make([]byte, 2*defaultMFTRecordSize+17)) calls := 0 err := walkRecords(reader, int64(2*defaultMFTRecordSize+17), defaultMFTRecordSize, func(b []byte) (Record, error) { calls++ if len(b) != int(defaultMFTRecordSize) { t.Fatalf("parser got len=%d, want %d", len(b), defaultMFTRecordSize) } return Record{}, nil }, func(Record) error { return nil }) if err != nil { t.Fatalf("walkRecords returned error: %v", err) } if calls != 2 { t.Fatalf("parser calls = %d, want 2", calls) } } func TestWalkRecordsPropagatesVisitorError(t *testing.T) { reader := bytes.NewReader(make([]byte, 2*defaultMFTRecordSize)) wantErr := errors.New("stop") visited := 0 err := walkRecords(reader, int64(2*defaultMFTRecordSize), defaultMFTRecordSize, func([]byte) (Record, error) { return Record{}, nil }, func(Record) error { visited++ if visited == 2 { return wantErr } return nil }) if !errors.Is(err, wantErr) { t.Fatalf("walkRecords error = %v, want %v", err, wantErr) } if visited != 2 { t.Fatalf("visited = %d, want 2", visited) } } type testChunkReader struct { chunks [][]byte index int } func (r *testChunkReader) Read(p []byte) (int, error) { if r.index >= len(r.chunks) { return 0, io.EOF } chunk := r.chunks[r.index] r.index++ copy(p, chunk) return len(chunk), nil } type testWriter struct { wrote int } func (w *testWriter) Write(p []byte) (int, error) { w.wrote += len(p) return len(p), nil } func testFileNameData(name string, parent uint64, namespace FileNameNamespace) []byte { encoded := utf16.Encode([]rune(name)) data := make([]byte, 66+len(encoded)*2) binary.LittleEndian.PutUint64(data[0:], parent) data[0x40] = byte(len(encoded)) data[0x41] = byte(namespace) for i, v := range encoded { binary.LittleEndian.PutUint16(data[0x42+i*2:], v) } return data }