package folder_test import ( "testing" "queryorchestration/internal/database/repository" "queryorchestration/internal/fieldextraction" "queryorchestration/internal/folder" "queryorchestration/internal/serviceconfig" "queryorchestration/internal/test" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type TestConfig struct { serviceconfig.BaseConfig } func TestCreateFolder(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) // Create test client first clientID := "test-client-folders" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Folders", }) require.NoError(t, err) t.Run("create root folder successfully", func(t *testing.T) { path := "/documents" createdBy := "user123" result, err := svc.CreateFolder(ctx, path, nil, clientID, createdBy) require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, path, result.Path) assert.Nil(t, result.Parentid) assert.Equal(t, clientID, result.Clientid) assert.Equal(t, createdBy, result.Createdby) }) t.Run("create subfolder with valid parent", func(t *testing.T) { // Create parent folder parentPath := "/contracts" parentResult, err := svc.CreateFolder(ctx, parentPath, nil, clientID, "user123") require.NoError(t, err) parentID := parentResult.ID // Create subfolder subPath := "/contracts/2025" result, err := svc.CreateFolder(ctx, subPath, &parentID, clientID, "user123") require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, subPath, result.Path) require.NotNil(t, result.Parentid) assert.Equal(t, parentID, *result.Parentid) }) t.Run("reject empty path", func(t *testing.T) { _, err := svc.CreateFolder(ctx, "", nil, clientID, "user123") require.Error(t, err) assert.Contains(t, err.Error(), "must start with /") }) t.Run("reject empty clientID", func(t *testing.T) { _, err := svc.CreateFolder(ctx, "/test", nil, "", "user123") require.Error(t, err) assert.Contains(t, err.Error(), "clientID cannot be empty") }) t.Run("reject empty createdBy", func(t *testing.T) { _, err := svc.CreateFolder(ctx, "/test", nil, clientID, "") require.Error(t, err) assert.Contains(t, err.Error(), "createdBy cannot be empty") }) t.Run("reject parent from different client", func(t *testing.T) { // Create another client otherClientID := "other-client" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: otherClientID, Name: "Other Client", }) require.NoError(t, err) // Create parent folder for other client parentResult, err := svc.CreateFolder(ctx, "/other", nil, otherClientID, "user123") require.NoError(t, err) parentID := parentResult.ID // Try to create subfolder with parent from different client _, err = svc.CreateFolder(ctx, "/test", &parentID, clientID, "user123") require.Error(t, err) assert.Contains(t, err.Error(), "different client") }) } func TestGetFolder(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-get" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Get", }) require.NoError(t, err) // Create a folder path := "/documents" created, err := svc.CreateFolder(ctx, path, nil, clientID, "user123") require.NoError(t, err) // Get the folder result, err := svc.GetFolder(ctx, created.ID) require.NoError(t, err) assert.Equal(t, created.ID, result.ID) assert.Equal(t, path, result.Path) } func TestGetFolderByPath(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-path" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Path", }) require.NoError(t, err) // Create a folder path := "/documents/2025" created, err := svc.CreateFolder(ctx, path, nil, clientID, "user123") require.NoError(t, err) // Get by path result, err := svc.GetFolderByPath(ctx, clientID, path) require.NoError(t, err) assert.Equal(t, created.ID, result.ID) assert.Equal(t, path, result.Path) assert.Equal(t, clientID, result.Clientid) } func TestRenameFolder(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-rename" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Rename", }) require.NoError(t, err) t.Run("rename successfully", func(t *testing.T) { oldPath := "/old-path" created, err := svc.CreateFolder(ctx, oldPath, nil, clientID, "user123") require.NoError(t, err) newPath := "/new-path" err = svc.RenameFolder(ctx, created.ID, newPath) require.NoError(t, err) // Verify rename result, err := svc.GetFolder(ctx, created.ID) require.NoError(t, err) assert.Equal(t, newPath, result.Path) }) t.Run("reject empty path", func(t *testing.T) { err := svc.RenameFolder(ctx, uuid.New(), "") require.Error(t, err) // The folder won't be found, so we can't test path validation assert.Contains(t, err.Error(), "folder not found") }) t.Run("reject invalid path characters", func(t *testing.T) { path := "/test-rename-invalid" created, err := svc.CreateFolder(ctx, path, nil, clientID, "user123") require.NoError(t, err) err = svc.RenameFolder(ctx, created.ID, "/invalid@path") require.Error(t, err) assert.True(t, folder.IsPathValidationError(err)) }) t.Run("reject root folder rename", func(t *testing.T) { // Create root folder rootPath := "/" rootFolder, err := svc.CreateFolder(ctx, rootPath, nil, clientID, "user123") require.NoError(t, err) // Try to rename root folder err = svc.RenameFolder(ctx, rootFolder.ID, "/renamed-root") require.Error(t, err) assert.True(t, folder.IsPathValidationError(err)) assert.Contains(t, err.Error(), "root folder cannot be renamed") }) } func TestGetFolderTree(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-tree" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Tree", }) require.NoError(t, err) // Create folder hierarchy root, err := svc.CreateFolder(ctx, "/documents", nil, clientID, "user123") require.NoError(t, err) child1, err := svc.CreateFolder(ctx, "/documents/2025", &root.ID, clientID, "user123") require.NoError(t, err) _, err = svc.CreateFolder(ctx, "/documents/2025/january", &child1.ID, clientID, "user123") require.NoError(t, err) // Get tree result, err := svc.GetFolderTree(ctx, root.ID) require.NoError(t, err) assert.Len(t, result, 3) // root + 2 descendants assert.Equal(t, root.ID, result[0].ID) } func TestGetFoldersByClient(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-list" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client List", }) require.NoError(t, err) // Create multiple folders _, err = svc.CreateFolder(ctx, "/documents", nil, clientID, "user123") require.NoError(t, err) _, err = svc.CreateFolder(ctx, "/contracts", nil, clientID, "user123") require.NoError(t, err) result, err := svc.GetFoldersByClient(ctx, clientID) require.NoError(t, err) assert.GreaterOrEqual(t, len(result), 2) } func TestGetRootFolders(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-roots" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Roots", }) require.NoError(t, err) // Create root folder root, err := svc.CreateFolder(ctx, "/documents", nil, clientID, "user123") require.NoError(t, err) // Create child folder _, err = svc.CreateFolder(ctx, "/documents/sub", &root.ID, clientID, "user123") require.NoError(t, err) // Get only root folders result, err := svc.GetRootFolders(ctx, clientID) require.NoError(t, err) // Filter to only this test's root var foundRoot *repository.Folder for _, f := range result { if f.ID == root.ID { foundRoot = f break } } require.NotNil(t, foundRoot) assert.Nil(t, foundRoot.Parentid) } func TestGetSubfolders(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-subs" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Subs", }) require.NoError(t, err) // Create parent parent, err := svc.CreateFolder(ctx, "/documents", nil, clientID, "user123") require.NoError(t, err) // Create child child, err := svc.CreateFolder(ctx, "/documents/2025", &parent.ID, clientID, "user123") require.NoError(t, err) // Get subfolders result, err := svc.GetSubfolders(ctx, parent.ID) require.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, child.ID, result[0].ID) assert.Equal(t, parent.ID, *result[0].Parentid) } func TestGetDocumentsByFolder(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-docs-by-folder" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Docs By Folder", }) require.NoError(t, err) t.Run("get documents in folder", func(t *testing.T) { // Create folder folderResult, err := svc.CreateFolder(ctx, "/contracts", nil, clientID, "user123") require.NoError(t, err) // Create documents in folder filename1 := "contract1.pdf" doc1ID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "foldertest1", Filename: &filename1, Folderid: &folderResult.ID, }) require.NoError(t, err) filename2 := "contract2.pdf" doc2ID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "foldertest2", Filename: &filename2, Folderid: &folderResult.ID, }) require.NoError(t, err) // Get documents by folder docs, err := svc.GetDocumentsByFolder(ctx, folderResult.ID) require.NoError(t, err) assert.Len(t, docs, 2) // Verify both documents are returned docIDs := make(map[uuid.UUID]bool) for _, doc := range docs { docIDs[doc.ID] = true } assert.True(t, docIDs[doc1ID]) assert.True(t, docIDs[doc2ID]) }) t.Run("return empty for folder with no documents", func(t *testing.T) { // Create folder with no documents emptyFolder, err := svc.CreateFolder(ctx, "/empty", nil, clientID, "user123") require.NoError(t, err) docs, err := svc.GetDocumentsByFolder(ctx, emptyFolder.ID) require.NoError(t, err) assert.Len(t, docs, 0) }) } func TestGetDocumentsEnriched(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := folder.New(cfg) clientID := "test-client-docs-enriched" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Docs Enriched", }) require.NoError(t, err) t.Run("get enriched documents without text record", func(t *testing.T) { // Create folder folderResult, err := svc.CreateFolder(ctx, "/enriched-no-text", nil, clientID, "user123") require.NoError(t, err) // Create document in folder filename := "enriched1.pdf" originalPath := "/uploads/enriched1.pdf" docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedtest1", Filename: &filename, Originalpath: &originalPath, Folderid: &folderResult.ID, }) require.NoError(t, err) // Get enriched documents docs, err := svc.GetDocumentsEnriched(ctx, folderResult.ID, false) require.NoError(t, err) assert.Len(t, docs, 1) assert.Equal(t, docID, docs[0].ID) assert.Equal(t, "enrichedtest1", docs[0].Hash) assert.Equal(t, filename, *docs[0].Filename) assert.Equal(t, originalPath, *docs[0].OriginalPath) assert.False(t, docs[0].HasTextRecord) assert.Nil(t, docs[0].TextRecord) assert.Empty(t, docs[0].Labels) }) t.Run("get enriched documents with text record", func(t *testing.T) { // Create folder folderResult, err := svc.CreateFolder(ctx, "/enriched-with-text", nil, clientID, "user123") require.NoError(t, err) // Create document in folder filename := "enriched2.pdf" docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedtest2", Filename: &filename, Folderid: &folderResult.ID, }) require.NoError(t, err) // Create field extraction for this document fieldSvc := fieldextraction.New(cfg) contractTitle := "Enriched Test Contract" input := &fieldextraction.CreateFieldExtractionInput{ DocumentID: docID, SingleFields: &repository.AddFieldExtractionParams{ Documentid: docID, Filename: &filename, Contracttitle: &contractTitle, Createdby: "testuser", }, ArrayFields: []*repository.AddFieldExtractionArrayFieldParams{}, } _, err = fieldSvc.CreateFieldExtraction(ctx, input) require.NoError(t, err) // Get enriched documents with text record docs, err := svc.GetDocumentsEnriched(ctx, folderResult.ID, true) require.NoError(t, err) assert.Len(t, docs, 1) assert.Equal(t, docID, docs[0].ID) assert.True(t, docs[0].HasTextRecord) assert.NotNil(t, docs[0].TextRecord) assert.Equal(t, docID, docs[0].TextRecord.DocumentID) assert.Equal(t, int32(1), docs[0].TextRecord.Version) assert.Equal(t, "testuser", docs[0].TextRecord.CreatedBy) }) t.Run("get enriched documents with text record but not requesting it", func(t *testing.T) { // Create folder folderResult, err := svc.CreateFolder(ctx, "/enriched-skip-text", nil, clientID, "user123") require.NoError(t, err) // Create document in folder filename := "enriched3.pdf" docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedtest3", Filename: &filename, Folderid: &folderResult.ID, }) require.NoError(t, err) // Create field extraction fieldSvc := fieldextraction.New(cfg) contractTitle := "Skip Text Contract" input := &fieldextraction.CreateFieldExtractionInput{ DocumentID: docID, SingleFields: &repository.AddFieldExtractionParams{ Documentid: docID, Filename: &filename, Contracttitle: &contractTitle, Createdby: "skipuser", }, ArrayFields: []*repository.AddFieldExtractionArrayFieldParams{}, } _, err = fieldSvc.CreateFieldExtraction(ctx, input) require.NoError(t, err) // Get enriched documents without requesting text record docs, err := svc.GetDocumentsEnriched(ctx, folderResult.ID, false) require.NoError(t, err) assert.Len(t, docs, 1) assert.True(t, docs[0].HasTextRecord) // Has text record assert.Nil(t, docs[0].TextRecord) // But not fetched }) t.Run("return empty for empty folder", func(t *testing.T) { // Create folder with no documents emptyFolder, err := svc.CreateFolder(ctx, "/enriched-empty", nil, clientID, "user123") require.NoError(t, err) docs, err := svc.GetDocumentsEnriched(ctx, emptyFolder.ID, false) require.NoError(t, err) assert.Len(t, docs, 0) }) }