Spaces:
Running
Running
Amlan-109
feat: Initial commit of LocalAI Amlan Edition with premium branding and personalization
750bbe6
| package hfapi_test | |
| import ( | |
| "fmt" | |
| "net/http" | |
| "net/http/httptest" | |
| "strings" | |
| . "github.com/onsi/ginkgo/v2" | |
| . "github.com/onsi/gomega" | |
| hfapi "github.com/mudler/LocalAI/pkg/huggingface-api" | |
| ) | |
| var _ = Describe("HuggingFace API Client", func() { | |
| var ( | |
| client *hfapi.Client | |
| server *httptest.Server | |
| ) | |
| BeforeEach(func() { | |
| client = hfapi.NewClient() | |
| }) | |
| AfterEach(func() { | |
| if server != nil { | |
| server.Close() | |
| } | |
| }) | |
| Context("when creating a new client", func() { | |
| It("should initialize with correct base URL", func() { | |
| Expect(client).ToNot(BeNil()) | |
| Expect(client.BaseURL()).To(Equal("https://huggingface.co/api/models")) | |
| }) | |
| }) | |
| Context("when searching for models", func() { | |
| BeforeEach(func() { | |
| // Mock response data | |
| mockResponse := `[ | |
| { | |
| "modelId": "test-model-1", | |
| "author": "test-author", | |
| "downloads": 1000, | |
| "lastModified": "2024-01-01T00:00:00.000Z", | |
| "pipelineTag": "text-generation", | |
| "private": false, | |
| "tags": ["gguf", "llama"], | |
| "createdAt": "2024-01-01T00:00:00.000Z", | |
| "updatedAt": "2024-01-01T00:00:00.000Z", | |
| "sha": "abc123", | |
| "config": {}, | |
| "model_index": "test-index", | |
| "library_name": "transformers", | |
| "mask_token": null, | |
| "tokenizer_class": "LlamaTokenizer" | |
| }, | |
| { | |
| "modelId": "test-model-2", | |
| "author": "test-author-2", | |
| "downloads": 2000, | |
| "lastModified": "2024-01-02T00:00:00.000Z", | |
| "pipelineTag": "text-generation", | |
| "private": false, | |
| "tags": ["gguf", "mistral"], | |
| "createdAt": "2024-01-02T00:00:00.000Z", | |
| "updatedAt": "2024-01-02T00:00:00.000Z", | |
| "sha": "def456", | |
| "config": {}, | |
| "model_index": "test-index-2", | |
| "library_name": "transformers", | |
| "mask_token": null, | |
| "tokenizer_class": "MistralTokenizer" | |
| } | |
| ]` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| // Verify request parameters | |
| Expect(r.URL.Query().Get("sort")).To(Equal("lastModified")) | |
| Expect(r.URL.Query().Get("direction")).To(Equal("-1")) | |
| Expect(r.URL.Query().Get("limit")).To(Equal("30")) | |
| Expect(r.URL.Query().Get("search")).To(Equal("GGUF")) | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockResponse)) | |
| })) | |
| // Override the client's base URL to use our mock server | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should successfully search for models", func() { | |
| params := hfapi.SearchParams{ | |
| Sort: "lastModified", | |
| Direction: -1, | |
| Limit: 30, | |
| Search: "GGUF", | |
| } | |
| models, err := client.SearchModels(params) | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(models).To(HaveLen(2)) | |
| // Verify first model | |
| Expect(models[0].ModelID).To(Equal("test-model-1")) | |
| Expect(models[0].Author).To(Equal("test-author")) | |
| Expect(models[0].Downloads).To(Equal(1000)) | |
| Expect(models[0].PipelineTag).To(Equal("text-generation")) | |
| Expect(models[0].Private).To(BeFalse()) | |
| Expect(models[0].Tags).To(ContainElements("gguf", "llama")) | |
| // Verify second model | |
| Expect(models[1].ModelID).To(Equal("test-model-2")) | |
| Expect(models[1].Author).To(Equal("test-author-2")) | |
| Expect(models[1].Downloads).To(Equal(2000)) | |
| Expect(models[1].Tags).To(ContainElements("gguf", "mistral")) | |
| }) | |
| It("should handle empty search results", func() { | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte("[]")) | |
| })) | |
| client.SetBaseURL(server.URL) | |
| params := hfapi.SearchParams{ | |
| Sort: "lastModified", | |
| Direction: -1, | |
| Limit: 30, | |
| Search: "nonexistent", | |
| } | |
| models, err := client.SearchModels(params) | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(models).To(HaveLen(0)) | |
| }) | |
| It("should handle HTTP errors", func() { | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| w.WriteHeader(http.StatusInternalServerError) | |
| w.Write([]byte("Internal Server Error")) | |
| })) | |
| client.SetBaseURL(server.URL) | |
| params := hfapi.SearchParams{ | |
| Sort: "lastModified", | |
| Direction: -1, | |
| Limit: 30, | |
| Search: "GGUF", | |
| } | |
| models, err := client.SearchModels(params) | |
| Expect(err).To(HaveOccurred()) | |
| Expect(err.Error()).To(ContainSubstring("Status code: 500")) | |
| Expect(models).To(BeNil()) | |
| }) | |
| It("should handle malformed JSON response", func() { | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte("invalid json")) | |
| })) | |
| client.SetBaseURL(server.URL) | |
| params := hfapi.SearchParams{ | |
| Sort: "lastModified", | |
| Direction: -1, | |
| Limit: 30, | |
| Search: "GGUF", | |
| } | |
| models, err := client.SearchModels(params) | |
| Expect(err).To(HaveOccurred()) | |
| Expect(err.Error()).To(ContainSubstring("failed to parse JSON response")) | |
| Expect(models).To(BeNil()) | |
| }) | |
| }) | |
| Context("when getting latest GGUF models", func() { | |
| BeforeEach(func() { | |
| mockResponse := `[ | |
| { | |
| "modelId": "latest-gguf-model", | |
| "author": "gguf-author", | |
| "downloads": 5000, | |
| "lastModified": "2024-01-03T00:00:00.000Z", | |
| "pipelineTag": "text-generation", | |
| "private": false, | |
| "tags": ["gguf", "latest"], | |
| "createdAt": "2024-01-03T00:00:00.000Z", | |
| "updatedAt": "2024-01-03T00:00:00.000Z", | |
| "sha": "latest123", | |
| "config": {}, | |
| "model_index": "latest-index", | |
| "library_name": "transformers", | |
| "mask_token": null, | |
| "tokenizer_class": "LlamaTokenizer" | |
| } | |
| ]` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| // Verify the search parameters are correct for GGUF search | |
| Expect(r.URL.Query().Get("search")).To(Equal("GGUF")) | |
| Expect(r.URL.Query().Get("sort")).To(Equal("lastModified")) | |
| Expect(r.URL.Query().Get("direction")).To(Equal("-1")) | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockResponse)) | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should fetch latest GGUF models with correct parameters", func() { | |
| models, err := client.GetLatest("GGUF", 10) | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(models).To(HaveLen(1)) | |
| Expect(models[0].ModelID).To(Equal("latest-gguf-model")) | |
| Expect(models[0].Author).To(Equal("gguf-author")) | |
| Expect(models[0].Downloads).To(Equal(5000)) | |
| Expect(models[0].Tags).To(ContainElements("gguf", "latest")) | |
| }) | |
| It("should use custom search term", func() { | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| Expect(r.URL.Query().Get("search")).To(Equal("custom-search")) | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte("[]")) | |
| })) | |
| client.SetBaseURL(server.URL) | |
| models, err := client.GetLatest("custom-search", 5) | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(models).To(HaveLen(0)) | |
| }) | |
| }) | |
| Context("when handling network errors", func() { | |
| It("should handle connection failures gracefully", func() { | |
| // Use an invalid URL to simulate connection failure | |
| client.SetBaseURL("http://invalid-url-that-does-not-exist") | |
| params := hfapi.SearchParams{ | |
| Sort: "lastModified", | |
| Direction: -1, | |
| Limit: 30, | |
| Search: "GGUF", | |
| } | |
| models, err := client.SearchModels(params) | |
| Expect(err).To(HaveOccurred()) | |
| Expect(err.Error()).To(ContainSubstring("failed to make request")) | |
| Expect(models).To(BeNil()) | |
| }) | |
| }) | |
| Context("when getting file SHA on remote model", func() { | |
| It("should get file SHA successfully", func() { | |
| sha, err := client.GetFileSHA( | |
| "mudler/LocalAI-functioncall-qwen2.5-7b-v0.5-Q4_K_M-GGUF", "localai-functioncall-qwen2.5-7b-v0.5-q4_k_m.gguf") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(sha).To(Equal("4e7b7fe1d54b881f1ef90799219dc6cc285d29db24f559c8998d1addb35713d4")) | |
| }) | |
| }) | |
| Context("when listing files", func() { | |
| BeforeEach(func() { | |
| mockFilesResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "model-Q4_K_M.gguf", | |
| "size": 1000000, | |
| "oid": "abc123", | |
| "lfs": { | |
| "oid": "def456789", | |
| "size": 1000000, | |
| "pointerSize": 135 | |
| } | |
| }, | |
| { | |
| "type": "file", | |
| "path": "README.md", | |
| "size": 5000, | |
| "oid": "readme123" | |
| }, | |
| { | |
| "type": "file", | |
| "path": "config.json", | |
| "size": 1000, | |
| "oid": "config123" | |
| } | |
| ]` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if strings.Contains(r.URL.Path, "/tree/main") { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockFilesResponse)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should list files successfully", func() { | |
| files, err := client.ListFiles("test/model") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(files).To(HaveLen(3)) | |
| Expect(files[0].Path).To(Equal("model-Q4_K_M.gguf")) | |
| Expect(files[0].Size).To(Equal(int64(1000000))) | |
| Expect(files[0].LFS).ToNot(BeNil()) | |
| Expect(files[0].LFS.Oid).To(Equal("def456789")) | |
| Expect(files[1].Path).To(Equal("README.md")) | |
| Expect(files[1].Size).To(Equal(int64(5000))) | |
| }) | |
| }) | |
| Context("when listing files with subfolders", func() { | |
| BeforeEach(func() { | |
| // Mock response for root directory with files and a subfolder | |
| mockRootResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "README.md", | |
| "size": 5000, | |
| "oid": "readme123" | |
| }, | |
| { | |
| "type": "directory", | |
| "path": "subfolder", | |
| "size": 0, | |
| "oid": "dir123" | |
| }, | |
| { | |
| "type": "file", | |
| "path": "config.json", | |
| "size": 1000, | |
| "oid": "config123" | |
| } | |
| ]` | |
| // Mock response for subfolder directory | |
| mockSubfolderResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "subfolder/file.bin", | |
| "size": 2000000, | |
| "oid": "filebin123", | |
| "lfs": { | |
| "oid": "filebin456", | |
| "size": 2000000, | |
| "pointerSize": 135 | |
| } | |
| }, | |
| { | |
| "type": "directory", | |
| "path": "nested", | |
| "size": 0, | |
| "oid": "nesteddir123" | |
| } | |
| ]` | |
| // Mock response for nested subfolder | |
| mockNestedResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "subfolder/nested/nested_file.gguf", | |
| "size": 5000000, | |
| "oid": "nested123", | |
| "lfs": { | |
| "oid": "nested456", | |
| "size": 5000000, | |
| "pointerSize": 135 | |
| } | |
| } | |
| ]` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| urlPath := r.URL.Path | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| if strings.Contains(urlPath, "/tree/main/subfolder/nested") { | |
| w.Write([]byte(mockNestedResponse)) | |
| } else if strings.Contains(urlPath, "/tree/main/subfolder") { | |
| w.Write([]byte(mockSubfolderResponse)) | |
| } else if strings.Contains(urlPath, "/tree/main") { | |
| w.Write([]byte(mockRootResponse)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should recursively list all files including those in subfolders", func() { | |
| files, err := client.ListFiles("test/model") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(files).To(HaveLen(4)) | |
| // Verify root level files | |
| readmeFile := findFileByPath(files, "README.md") | |
| Expect(readmeFile).ToNot(BeNil()) | |
| Expect(readmeFile.Size).To(Equal(int64(5000))) | |
| Expect(readmeFile.Oid).To(Equal("readme123")) | |
| configFile := findFileByPath(files, "config.json") | |
| Expect(configFile).ToNot(BeNil()) | |
| Expect(configFile.Size).To(Equal(int64(1000))) | |
| Expect(configFile.Oid).To(Equal("config123")) | |
| // Verify subfolder file with relative path | |
| subfolderFile := findFileByPath(files, "subfolder/file.bin") | |
| Expect(subfolderFile).ToNot(BeNil()) | |
| Expect(subfolderFile.Size).To(Equal(int64(2000000))) | |
| Expect(subfolderFile.LFS).ToNot(BeNil()) | |
| Expect(subfolderFile.LFS.Oid).To(Equal("filebin456")) | |
| // Verify nested subfolder file | |
| nestedFile := findFileByPath(files, "subfolder/nested/nested_file.gguf") | |
| Expect(nestedFile).ToNot(BeNil()) | |
| Expect(nestedFile.Size).To(Equal(int64(5000000))) | |
| Expect(nestedFile.LFS).ToNot(BeNil()) | |
| Expect(nestedFile.LFS.Oid).To(Equal("nested456")) | |
| }) | |
| It("should handle files with correct relative paths", func() { | |
| files, err := client.ListFiles("test/model") | |
| Expect(err).ToNot(HaveOccurred()) | |
| // Check that all paths are relative and correct | |
| paths := make([]string, len(files)) | |
| for i, file := range files { | |
| paths[i] = file.Path | |
| } | |
| Expect(paths).To(ContainElements( | |
| "README.md", | |
| "config.json", | |
| "subfolder/file.bin", | |
| "subfolder/nested/nested_file.gguf", | |
| )) | |
| }) | |
| }) | |
| Context("when getting file SHA", func() { | |
| BeforeEach(func() { | |
| mockFilesResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "model-Q4_K_M.gguf", | |
| "size": 1000000, | |
| "oid": "abc123", | |
| "lfs": { | |
| "oid": "def456789", | |
| "size": 1000000, | |
| "pointerSize": 135 | |
| } | |
| } | |
| ]` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if strings.Contains(r.URL.Path, "/tree/main") { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockFilesResponse)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should get file SHA successfully", func() { | |
| sha, err := client.GetFileSHA("test/model", "model-Q4_K_M.gguf") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(sha).To(Equal("def456789")) | |
| }) | |
| It("should handle missing SHA gracefully", func() { | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if strings.Contains(r.URL.Path, "/tree/main") { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(`[ | |
| { | |
| "type": "file", | |
| "path": "file.txt", | |
| "size": 100, | |
| "oid": "file123" | |
| } | |
| ]`)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| sha, err := client.GetFileSHA("test/model", "file.txt") | |
| Expect(err).ToNot(HaveOccurred()) | |
| // When there's no LFS, it should return the OID | |
| Expect(sha).To(Equal("file123")) | |
| }) | |
| }) | |
| Context("when getting model details", func() { | |
| BeforeEach(func() { | |
| mockFilesResponse := `[ | |
| { | |
| "type": "file", | |
| "path": "model-Q4_K_M.gguf", | |
| "size": 1000000, | |
| "oid": "abc123", | |
| "lfs": { | |
| "oid": "sha256:def456", | |
| "size": 1000000, | |
| "pointer": "version https://git-lfs.github.com/spec/v1", | |
| "sha256": "def456789" | |
| } | |
| }, | |
| { | |
| "type": "file", | |
| "path": "README.md", | |
| "size": 5000, | |
| "oid": "readme123" | |
| } | |
| ]` | |
| mockFileInfoResponse := `{ | |
| "path": "model-Q4_K_M.gguf", | |
| "size": 1000000, | |
| "oid": "abc123", | |
| "lfs": { | |
| "oid": "sha256:def456", | |
| "size": 1000000, | |
| "pointer": "version https://git-lfs.github.com/spec/v1", | |
| "sha256": "def456789" | |
| } | |
| }` | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if strings.Contains(r.URL.Path, "/tree/main") { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockFilesResponse)) | |
| } else if strings.Contains(r.URL.Path, "/paths-info") { | |
| w.Header().Set("Content-Type", "application/json") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockFileInfoResponse)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should get model details successfully", func() { | |
| details, err := client.GetModelDetails("test/model") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(details.ModelID).To(Equal("test/model")) | |
| Expect(details.Author).To(Equal("test")) | |
| Expect(details.Files).To(HaveLen(2)) | |
| Expect(details.ReadmeFile).ToNot(BeNil()) | |
| Expect(details.ReadmeFile.Path).To(Equal("README.md")) | |
| Expect(details.ReadmeFile.IsReadme).To(BeTrue()) | |
| // Verify URLs are set for all files | |
| baseURL := strings.TrimSuffix(server.URL, "/api/models") | |
| for _, file := range details.Files { | |
| expectedURL := fmt.Sprintf("%s/test/model/resolve/main/%s", baseURL, file.Path) | |
| Expect(file.URL).To(Equal(expectedURL)) | |
| } | |
| }) | |
| }) | |
| Context("when getting README content", func() { | |
| BeforeEach(func() { | |
| mockReadmeContent := "# Test Model\n\nThis is a test model for demonstration purposes." | |
| server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| if strings.Contains(r.URL.Path, "/raw/main/") { | |
| w.Header().Set("Content-Type", "text/plain") | |
| w.WriteHeader(http.StatusOK) | |
| w.Write([]byte(mockReadmeContent)) | |
| } else { | |
| w.WriteHeader(http.StatusNotFound) | |
| } | |
| })) | |
| client.SetBaseURL(server.URL) | |
| }) | |
| It("should get README content successfully", func() { | |
| content, err := client.GetReadmeContent("test/model", "README.md") | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(content).To(Equal("# Test Model\n\nThis is a test model for demonstration purposes.")) | |
| }) | |
| }) | |
| Context("when filtering files", func() { | |
| It("should filter files by quantization", func() { | |
| files := []hfapi.ModelFile{ | |
| {Path: "model-Q4_K_M.gguf"}, | |
| {Path: "model-Q3_K_M.gguf"}, | |
| {Path: "README.md", IsReadme: true}, | |
| } | |
| filtered := hfapi.FilterFilesByQuantization(files, "Q4_K_M") | |
| Expect(filtered).To(HaveLen(1)) | |
| Expect(filtered[0].Path).To(Equal("model-Q4_K_M.gguf")) | |
| }) | |
| It("should find preferred model file", func() { | |
| files := []hfapi.ModelFile{ | |
| {Path: "model-Q3_K_M.gguf"}, | |
| {Path: "model-Q4_K_M.gguf"}, | |
| {Path: "README.md", IsReadme: true}, | |
| } | |
| preferences := []string{"Q4_K_M", "Q3_K_M"} | |
| preferred := hfapi.FindPreferredModelFile(files, preferences) | |
| Expect(preferred).ToNot(BeNil()) | |
| Expect(preferred.Path).To(Equal("model-Q4_K_M.gguf")) | |
| Expect(preferred.IsReadme).To(BeFalse()) | |
| }) | |
| It("should return nil if no preferred file found", func() { | |
| files := []hfapi.ModelFile{ | |
| {Path: "model-Q2_K.gguf"}, | |
| {Path: "README.md", IsReadme: true}, | |
| } | |
| preferences := []string{"Q4_K_M", "Q3_K_M"} | |
| preferred := hfapi.FindPreferredModelFile(files, preferences) | |
| Expect(preferred).To(BeNil()) | |
| }) | |
| }) | |
| Context("integration test with real HuggingFace API", func() { | |
| It("should recursively list all files including subfolders from real repository", func() { | |
| // This test makes actual API calls to HuggingFace | |
| // Skip if running in CI or if network is not available | |
| realClient := hfapi.NewClient() | |
| repoID := "bartowski/Qwen_Qwen3-Next-80B-A3B-Instruct-GGUF" | |
| files, err := realClient.ListFiles(repoID) | |
| Expect(err).ToNot(HaveOccurred()) | |
| Expect(files).ToNot(BeEmpty(), "should return at least some files") | |
| // Verify that we get files from subfolders | |
| // Based on the repository structure, there should be files in subfolders like: | |
| // - Qwen_Qwen3-Next-80B-A3B-Instruct-Q4_1/... | |
| // - Qwen_Qwen3-Next-80B-A3B-Instruct-Q5_K_L/... | |
| // etc. | |
| hasSubfolderFiles := false | |
| rootLevelFiles := 0 | |
| subfolderFiles := 0 | |
| for _, file := range files { | |
| if strings.Contains(file.Path, "/") { | |
| hasSubfolderFiles = true | |
| subfolderFiles++ | |
| // Verify the path format is correct (subfolder/file.gguf) | |
| Expect(file.Path).ToNot(HavePrefix("/"), "paths should be relative, not absolute") | |
| Expect(file.Path).ToNot(HaveSuffix("/"), "file paths should not end with /") | |
| } else { | |
| rootLevelFiles++ | |
| } | |
| } | |
| Expect(hasSubfolderFiles).To(BeTrue(), "should find files in subfolders") | |
| Expect(rootLevelFiles).To(BeNumerically(">", 0), "should find files at root level") | |
| Expect(subfolderFiles).To(BeNumerically(">", 0), "should find files in subfolders") | |
| // Verify specific expected files exist | |
| // Root level files | |
| readmeFile := findFileByPath(files, "README.md") | |
| Expect(readmeFile).ToNot(BeNil(), "README.md should exist at root level") | |
| // Verify we can find files in subfolders | |
| // Look for any file in a subfolder (the exact structure may vary, can be nested) | |
| foundSubfolderFile := false | |
| for _, file := range files { | |
| if strings.Contains(file.Path, "/") && strings.HasSuffix(file.Path, ".gguf") { | |
| foundSubfolderFile = true | |
| // Verify the path structure: can be nested like subfolder/subfolder/file.gguf | |
| parts := strings.Split(file.Path, "/") | |
| Expect(len(parts)).To(BeNumerically(">=", 2), "subfolder files should have at least subfolder/file.gguf format") | |
| // The last part should be the filename | |
| Expect(parts[len(parts)-1]).To(HaveSuffix(".gguf"), "file in subfolder should be a .gguf file") | |
| Expect(parts[len(parts)-1]).ToNot(BeEmpty(), "filename should not be empty") | |
| break | |
| } | |
| } | |
| Expect(foundSubfolderFile).To(BeTrue(), "should find at least one .gguf file in a subfolder") | |
| // Verify file properties are populated | |
| for _, file := range files { | |
| Expect(file.Path).ToNot(BeEmpty(), "file path should not be empty") | |
| Expect(file.Type).To(Equal("file"), "all returned items should be files, not directories") | |
| // Size might be 0 for some files, but OID should be present | |
| if file.LFS == nil { | |
| Expect(file.Oid).ToNot(BeEmpty(), "file should have an OID if no LFS") | |
| } | |
| } | |
| }) | |
| }) | |
| }) | |
| // findFileByPath is a helper function to find a file by its path in a slice of FileInfo | |
| func findFileByPath(files []hfapi.FileInfo, path string) *hfapi.FileInfo { | |
| for i := range files { | |
| if files[i].Path == path { | |
| return &files[i] | |
| } | |
| } | |
| return nil | |
| } | |