From a83061e1b9138c4993cf6f808c619cdb82da0a79 Mon Sep 17 00:00:00 2001 From: Lalit Kishore Date: Thu, 1 Oct 2026 21:15:24 +0530 Subject: [PATCH 1/2] fix: import primary artifacts before secondary artifacts Signed-off-by: Lalit Kishore --- cmd/importDir.go | 6 ++ cmd/importDir_order_test.go | 121 ++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 cmd/importDir_order_test.go diff --git a/cmd/importDir.go b/cmd/importDir.go index 0fbff13a..a7d2f2c6 100644 --- a/cmd/importDir.go +++ b/cmd/importDir.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "github.com/microcks/microcks-cli/pkg/connectors" @@ -211,6 +212,11 @@ func ImportDirectory(client MicrocksClient, fs FileSystem, dirPath string, confi Errors: make([]string, 0, len(files)), } + // Secondary artifacts can only enrich API definitions that already exist. + sort.SliceStable(files, func(i, j int) bool { + return detectFileType(files[i]).IsPrimary && !detectFileType(files[j]).IsPrimary + }) + for _, file := range files { fileType := detectFileType(file) diff --git a/cmd/importDir_order_test.go b/cmd/importDir_order_test.go new file mode 100644 index 00000000..4da378e1 --- /dev/null +++ b/cmd/importDir_order_test.go @@ -0,0 +1,121 @@ +/* + * Copyright The Microcks Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type orderedImportClient struct { + MockMicrocksClient + primary []bool +} + +func (c *orderedImportClient) UploadArtifact(file string, primary bool) (string, error) { + c.primary = append(c.primary, primary) + return c.MockMicrocksClient.UploadArtifact(file, primary) +} + +func TestImportDirectoryPrimaryOrder(t *testing.T) { + tests := []struct { + name string + files []string + config ImportConfig + wantOrder []string + wantPrimary []bool + failedFile string + }{ + { + name: "primary artifacts precede secondary artifacts with stable order", + files: []string{"a-examples.yaml", "b-openapi.yaml", "c-collection.json", "d-swagger.json", "e-metadata.yaml"}, + wantOrder: []string{"b-openapi.yaml", "d-swagger.json", "a-examples.yaml", "c-collection.json", "e-metadata.yaml"}, + wantPrimary: []bool{true, true, false, false, false}, + }, + { + name: "primary artifacts in later directories are imported first", + files: []string{"a/examples.yaml", "z/openapi.yaml"}, + config: ImportConfig{Recursive: true}, + wantOrder: []string{"z/openapi.yaml", "a/examples.yaml"}, + wantPrimary: []bool{true, false}, + }, + { + name: "primary-only order is preserved", + files: []string{"a-openapi.yaml", "b-swagger.json"}, + wantOrder: []string{"a-openapi.yaml", "b-swagger.json"}, + wantPrimary: []bool{true, true}, + }, + { + name: "secondary-only order is preserved", + files: []string{"a-examples.yaml", "b-collection.json"}, + wantOrder: []string{"a-examples.yaml", "b-collection.json"}, + wantPrimary: []bool{false, false}, + }, + { + name: "pattern and nonrecursive filters are preserved", + files: []string{"a-examples.yaml", "b-openapi.yaml", "c-collection.json", "nested/swagger.yaml"}, + config: ImportConfig{Pattern: "*.yaml"}, + wantOrder: []string{"b-openapi.yaml", "a-examples.yaml"}, + wantPrimary: []bool{true, false}, + }, + { + name: "upload failure preserves accounting and continues in order", + files: []string{"a-examples.yaml", "b-openapi.yaml", "c-swagger.json"}, + wantOrder: []string{"b-openapi.yaml", "c-swagger.json", "a-examples.yaml"}, + wantPrimary: []bool{true, true, false}, + failedFile: "b-openapi.yaml", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + for _, name := range tt.files { + path := filepath.Join(dir, name) + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0700)) + require.NoError(t, os.WriteFile(path, []byte("fixture"), 0600)) + } + client := &orderedImportClient{} + var wantSuccess, wantFailed, wantErrors []string + wantUploads := make([]string, 0, len(tt.wantOrder)) + for _, name := range tt.wantOrder { + path := filepath.Join(dir, name) + wantUploads = append(wantUploads, path) + if name == tt.failedFile { + client.FailedFiles = map[string]error{path: fmt.Errorf("upload failed")} + wantFailed = append(wantFailed, path) + wantErrors = append(wantErrors, fmt.Sprintf("error importing %s: upload failed", path)) + } else { + wantSuccess = append(wantSuccess, path) + } + } + result, err := ImportDirectory(client, &RealFileSystem{}, dir, tt.config) + require.NoError(t, err) + assert.Equal(t, wantUploads, client.Uploaded) + assert.Equal(t, tt.wantPrimary, client.primary) + assert.Equal(t, len(wantUploads), result.TotalFiles) + assert.Equal(t, len(wantSuccess), result.SuccessCount) + assert.Equal(t, len(wantFailed), result.FailedCount) + assert.Equal(t, wantSuccess, result.SuccessFiles) + assert.Equal(t, append([]string{}, wantFailed...), result.FailedFiles) + assert.Equal(t, append([]string{}, wantErrors...), result.Errors) + }) + } +} From 058e3dcc80e572768345d9feeb49a101bf58f49d Mon Sep 17 00:00:00 2001 From: Lalit Kishore Date: Wed, 7 Oct 2026 11:58:40 +0530 Subject: [PATCH 2/2] refactor: partition import artifacts in linear time Signed-off-by: Lalit Kishore --- cmd/importDir.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/importDir.go b/cmd/importDir.go index a7d2f2c6..710897aa 100644 --- a/cmd/importDir.go +++ b/cmd/importDir.go @@ -19,7 +19,6 @@ import ( "fmt" "os" "path/filepath" - "sort" "strings" "github.com/microcks/microcks-cli/pkg/connectors" @@ -213,11 +212,17 @@ func ImportDirectory(client MicrocksClient, fs FileSystem, dirPath string, confi } // Secondary artifacts can only enrich API definitions that already exist. - sort.SliceStable(files, func(i, j int) bool { - return detectFileType(files[i]).IsPrimary && !detectFileType(files[j]).IsPrimary - }) - + var primary, secondary []string for _, file := range files { + if detectFileType(file).IsPrimary { + primary = append(primary, file) + } else { + secondary = append(secondary, file) + } + } + orderedFiles := append(primary, secondary...) + + for _, file := range orderedFiles { fileType := detectFileType(file) msg, err := client.UploadArtifact(file, fileType.IsPrimary)