package store import ( "errors" "os" "strings" "testing" ) func mustFolder(t *testing.T, s *Store, name, parent string) *Folder { t.Helper() f, err := s.CreateFolder(name, parent) if err != nil { t.Fatalf("CreateFolder(%q): %v", name, err) } return f } func strPtr(s string) *string { return &s } // writeBlobForTest grava um arquivo bruto no dir de blobs (simula órfão). func writeBlobForTest(s *Store, path string) error { return os.WriteFile(path, []byte("orfao"), 0o640) } func TestAddFileValidatesAndServesBlob(t *testing.T) { s := newTestStore(t) folder := mustFolder(t, s, "docs", "") m, err := s.AddFile("rel.pdf", folder.ID, "", strings.NewReader("%PDF-1.4 fake")) if err != nil { t.Fatal(err) } if m.Size == 0 || m.MIME == "" || m.SHA256 == "" { t.Fatalf("metadado incompleto: %+v", m) } if got := s.RootFiles(); len(got) != 0 { t.Fatalf("arquivo com pasta não deveria estar na raiz: %d", len(got)) } if _, err := s.AddFile("x.txt", "pasta-inexistente", "", strings.NewReader("x")); !errors.Is(err, ErrInvalid) { t.Fatalf("pasta inexistente deveria dar ErrInvalid, deu %v", err) } if _, err := s.AddFile("y.txt", "", "nota-inexistente", strings.NewReader("y")); !errors.Is(err, ErrInvalid) { t.Fatalf("nota inexistente deveria dar ErrInvalid, deu %v", err) } } func TestAddFileRejectsPerFileCap(t *testing.T) { s := newTestStore(t) if _, err := s.AddFile("big.bin", "", "", infiniteReader{}); !errors.Is(err, ErrTooLarge) { t.Fatalf("esperava ErrTooLarge, deu %v", err) } } // infiniteReader gera bytes infinitos (para estourar o cap sem alocar). type infiniteReader struct{} func (infiniteReader) Read(p []byte) (int, error) { for i := range p { p[i] = 'A' } return len(p), nil } func TestAddFileConcurrencyHonorsQuota(t *testing.T) { s := newTestStore(t) payload := strings.Repeat("A", 1<<20) // 1 MiB const n = 8 errs := make(chan error, n) for range n { go func() { _, err := s.AddFile("f.bin", "", "", strings.NewReader(payload)) errs <- err }() } for range n { if err := <-errs; err != nil { // Quota é 2 GiB: nenhum upload de 1 MiB deveria falhar aqui. t.Fatalf("erro inesperado: %v", err) } } removed, missing, err := s.ReconcileBlobs() if err != nil { t.Fatal(err) } if removed != 0 || len(missing) != 0 { t.Fatalf("reconciliação após uploads ok: removed=%d missing=%v", removed, missing) } } func TestDeleteFilePersistsBeforeUnlink(t *testing.T) { s := newTestStore(t) m, err := s.AddFile("a.txt", "", "", strings.NewReader("oi")) if err != nil { t.Fatal(err) } if err := s.DeleteFile(m.ID); err != nil { t.Fatal(err) } if _, err := s.GetFile(m.ID); !errors.Is(err, ErrNotFound) { t.Fatalf("esperava ErrNotFound após delete, deu %v", err) } if err := s.DeleteFile(m.ID); !errors.Is(err, ErrNotFound) { t.Fatalf("delete duplo deveria dar ErrNotFound, deu %v", err) } } func TestDeleteFolderPersistsBeforeUnlink(t *testing.T) { s := newTestStore(t) f := mustFolder(t, s, "p", "") if _, err := s.AddFile("a.txt", f.ID, "", strings.NewReader("conteudo")); err != nil { t.Fatal(err) } if err := s.DeleteFolder(f.ID); err != nil { t.Fatal(err) } if len(s.fileMetas) != 0 { t.Fatalf("arquivos deveriam ter sumido: %+v", s.fileMetas) } removed, missing, err := s.ReconcileBlobs() if err != nil { t.Fatal(err) } if removed != 0 || len(missing) != 0 { t.Fatalf("órfãos após delete limpo: removed=%d missing=%v", removed, missing) } } func TestReconcileBlobsRemovesOrphans(t *testing.T) { s := newTestStore(t) if _, err := s.AddFile("a.txt", "", "", strings.NewReader("conteudo")); err != nil { t.Fatal(err) } // Simula blob órfão de falha de commit: arquivo no disco sem metadado. if err := writeBlobForTest(s, s.fileBlobPath("sem-metadado", ".bin")); err != nil { t.Fatal(err) } removed, missing, err := s.ReconcileBlobs() if err != nil { t.Fatal(err) } if removed != 1 { t.Fatalf("esperava 1 órfão removido, removeu %d", removed) } if len(missing) != 0 { t.Fatalf("nenhum metadado deveria estar sem blob: %v", missing) } } func TestMoveFileValidation(t *testing.T) { s := newTestStore(t) m, err := s.AddFile("a.txt", "", "", strings.NewReader("x")) if err != nil { t.Fatal(err) } if _, err := s.MoveFile(m.ID, "", strPtr("pasta-inexistente"), nil); !errors.Is(err, ErrInvalid) { t.Fatalf("move para pasta inexistente deveria dar ErrInvalid, deu %v", err) } f := mustFolder(t, s, "ok", "") got, err := s.MoveFile(m.ID, "", &f.ID, nil) if err != nil { t.Fatal(err) } if got.Folder != f.ID { t.Fatalf("esperava pasta %s, veio %s", f.ID, got.Folder) } }