diff --git a/binding/client.go b/binding/client.go index 41b28d4a4..6b51dcb48 100644 --- a/binding/client.go +++ b/binding/client.go @@ -21,7 +21,7 @@ import ( pathlib "path" "path/filepath" "strings" - "time" + "time" ) const ( @@ -192,7 +192,6 @@ func (r *Client) Post(path string, object interface{}) (err error) { default: err = liberr.New(http.StatusText(status)) } - return } @@ -447,6 +446,29 @@ func (r *Client) FilePut(path, source string, object interface{}) (err error) { return } +// +// FilePost uploads a file. +// Returns the created File resource. +func (r *Client) FilePost(path, source string, object interface{}) (err error) { + isDir, nErr := r.IsDir(source, true) + if nErr != nil { + err = nErr + return + } + if isDir { + err = liberr.New("Must be regular file.") + return + } + fields := []Field{ + { + Name: api.FileField, + Path: source, + }, + } + err = r.FileSend(path, http.MethodPost, fields, object) + return +} + // // FileSend sends file upload from. func (r *Client) FileSend(path, method string, fields []Field, object interface{}) (err error) { diff --git a/docs/test-api-matrix.md b/docs/test-api-matrix.md index 1fc827eb4..78fdaf1ce 100644 --- a/docs/test-api-matrix.md +++ b/docs/test-api-matrix.md @@ -7,7 +7,7 @@ application|:white_check_mark: partially|:heavy_check_mark:|| bucket||||partially within application dependency|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| file|:heavy_check_mark:|:heavy_check_mark:|| -import|||| +import||:heavy_check_mark:|| review|||| **Controls**|||| businessservice|:heavy_check_mark:|:heavy_check_mark:|| diff --git a/test/api/importcsv/api_test.go b/test/api/importcsv/api_test.go new file mode 100644 index 000000000..3e7776ec1 --- /dev/null +++ b/test/api/importcsv/api_test.go @@ -0,0 +1,171 @@ +package importcsv + +import ( + "io/ioutil" + "os" + "testing" + "time" + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestImportCSV(t *testing.T) { + for _, r := range TestCases { + t.Run(r.FileName, func(t *testing.T) { + + // Upload CSV. + inputData := api.ImportSummary{} + assert.Must(t, Client.FilePost(api.UploadRoot, r.FileName, &inputData)) + + // Inject import summary id into Summary root + pathForImportSummary := binding.Path(api.SummaryRoot).Inject(binding.Params{api.ID: inputData.ID}) + + // Since uploading the CSV happens asynchronously we need to wait for the upload to check Applications and Dependencies. + time.Sleep(time.Second) + + var outputImportSummaries []api.ImportSummary + outputMatchingSummary := api.ImportSummary{} + for{ + assert.Should(t, Client.Get(api.SummariesRoot, &outputImportSummaries)) + for _, gotImport := range outputImportSummaries { + if uint(gotImport.ID) == inputData.ID { + outputMatchingSummary = gotImport + } + } + if(outputMatchingSummary.ValidCount + outputMatchingSummary.InvalidCount == len(r.ExpectedApplications)+len(r.ExpectedDependencies)){ + break + } + time.Sleep(time.Second) + } + + // Check list of Applications. + gotApps, _ := Application.List() + if len(gotApps) != len(r.ExpectedApplications) { + t.Errorf("Mismatch in number of imported Applications: Expected %d, Actual %d", len(r.ExpectedApplications), len(gotApps)) + } else { + for i, gotApp := range gotApps { + if r.ExpectedApplications[i].Name != gotApp.Name { + t.Errorf("Mismatch in name of imported Application: Expected %s, Actual %s", r.ExpectedApplications[i].Name, gotApp.Name) + } + if r.ExpectedApplications[i].Description != gotApp.Description { + t.Errorf("Mismatch in description of imported Application: Expected %s, Actual %s", r.ExpectedApplications[i].Description, gotApp.Description) + } + if r.ExpectedApplications[i].Repository.Kind != gotApp.Repository.Kind { + t.Errorf("Mismatch in repository's kind ofimported Application: Expected %s, Actual %s", r.ExpectedApplications[i].Repository.Kind, gotApp.Repository.Kind) + } + if r.ExpectedApplications[i].Repository.URL != gotApp.Repository.URL { + t.Errorf("Mismatch in repository's url of imported Application: Expected %s, Actual %s", r.ExpectedApplications[i].Repository.URL, gotApp.Repository.URL) + } + if r.ExpectedApplications[i].Binary != gotApp.Binary { + t.Errorf("Mismatch in binary of imported Application: Expected %s, Actual %s", r.ExpectedApplications[i].Binary, gotApp.Binary) + } + for j, tag := range r.ExpectedApplications[i].Tags { + if tag.Name != gotApp.Tags[j].Name { + t.Errorf("Mismatch in tag name of imported Application: Expected %s, Actual %s", tag.Name, gotApp.Tags[j].Name) + } + } + if r.ExpectedApplications[i].BusinessService.Name != gotApp.BusinessService.Name { + t.Errorf("Mismatch in name of the BusinessService of imported Application: Expected %s, Actual %s", r.ExpectedApplications[i].BusinessService.Name, gotApp.BusinessService.Name) + } + } + } + + // Check list of Dependencies. + gotDeps, _ := Dependency.List() + if len(gotDeps) != len(r.ExpectedDependencies) { + t.Errorf("Mismatch in number of imported Dependencies: Expected %d, Actual %d", len(r.ExpectedDependencies), len(gotDeps)) + } else { + for i, importedDep := range gotDeps { + if importedDep.To.Name != r.ExpectedDependencies[i].To.Name { + t.Errorf("Mismatch in imported Dependency: Expected %s, Actual %s", r.ExpectedDependencies[i].To.Name, importedDep.To.Name) + } + if importedDep.From.Name != r.ExpectedDependencies[i].From.Name { + t.Errorf("Mismatch in imported Dependency: Expected %s, Actual %s", r.ExpectedDependencies[i].From.Name, importedDep.From.Name) + } + } + } + + // Get summaries of the Input ID. + outputImportSummary := api.ImportSummary{} + assert.Should(t, Client.Get(pathForImportSummary, &outputImportSummary)) + + // Get all imports. + var outputImports []api.Import + assert.Should(t, Client.Get(api.ImportsRoot, &outputImports)) + + // Check for number of imports. + if len(outputImports) != len(r.ExpectedApplications)+len(r.ExpectedDependencies) { + t.Errorf("Mismatch in number of imports") + } + + // Checks for individual applications and dependencies. + j, k := 0, 0 + for _, imp := range outputImports { + if imp["recordType1"] == 1 && j < len(r.ExpectedApplications) { + // An Application with no dependencies. + if r.ExpectedApplications[j].Name != imp["applicationName"] { + t.Errorf("Mismatch in name of import: Expected %s, Actual %s", r.ExpectedApplications[j].Name, imp["applicationName"]) + } + if r.ExpectedApplications[j].Description != imp["description"] { + t.Errorf("Mismatch in name of import: Expected %s, Actual %s", r.ExpectedApplications[j].Description, imp["description"]) + } + if r.ExpectedApplications[j].BusinessService.Name != imp["businessService"] { + t.Errorf("Mismatch in name of import: Expected %s, Actual %s", r.ExpectedApplications[j].BusinessService.Name, imp["businessService"]) + } + j++ + } + if imp["recordType1"] == 2 && k < len(r.ExpectedDependencies) { + // An Application with Dependencies. + if r.ExpectedDependencies[k].From.Name != imp["applicationName"] { + t.Errorf("Mismatch in name of import: Expected %s, Actual %s", r.ExpectedDependencies[k].From.Name, imp["applicationName"]) + } + if r.ExpectedDependencies[k].To.Name != imp["dependency"] { + t.Errorf("Mismatch in name of import: Expected %s, Actual %s", r.ExpectedDependencies[k].To.Name, imp["dependency"]) + } + k++ + } + } + + // Download the csv. + pathToGotCSV := "downloadcsv.csv" + assert.Should(t, Client.FileGet(api.DownloadRoot, pathToGotCSV)) + + // Read the got CSV file. + gotCSV, err := ioutil.ReadFile(pathToGotCSV) + if err != nil { + t.Errorf("Error reading CSV: %s", pathToGotCSV) + } + gotCSVString := string(gotCSV) + + // Read the expected CSV file. + expectedCSV, err := ioutil.ReadFile(r.FileName) + if err != nil { + t.Errorf("Error reading CSV: %s", r.FileName) + } + expectedCSVString := string(expectedCSV) + if gotCSVString != expectedCSVString { + t.Errorf("The CSV files have different content %s and %s", gotCSVString, expectedCSVString) + } + + // Remove the CSV file created. + err = os.Remove(pathToGotCSV) + if err != nil { + t.Errorf(err.Error()) + } + + // Delete imported summaries + assert.Must(t, Client.Delete(pathForImportSummary)) + + // Delete imported Applications. + for _, apps := range gotApps { + assert.Must(t, Application.Delete(apps.ID)) + } + + // Delete imported Dependencies. + for _, deps := range gotDeps { + assert.Must(t, Dependency.Delete(deps.ID)) + } + }) + } +} diff --git a/test/api/importcsv/pkg.go b/test/api/importcsv/pkg.go new file mode 100644 index 000000000..86dd7663f --- /dev/null +++ b/test/api/importcsv/pkg.go @@ -0,0 +1,27 @@ +package importcsv + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + Client *binding.Client + Application binding.Application + Dependency binding.Dependency +) + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Access REST client directly + Client = RichClient.Client + + // Access Application directly + Application = RichClient.Application + + // Access Dependency directly + Dependency = RichClient.Dependency +} diff --git a/test/api/importcsv/samples.go b/test/api/importcsv/samples.go new file mode 100644 index 000000000..56ee10628 --- /dev/null +++ b/test/api/importcsv/samples.go @@ -0,0 +1,137 @@ +package importcsv + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +type TestCase struct { + FileName string + ExpectedApplications []api.Application + ExpectedDependencies []api.Dependency +} + +var ( + TestCases = []TestCase{ + { + FileName: "template_application_import.csv", + ExpectedApplications: []api.Application{ + { + Name: "Customers", + Description: "Legacy Customers management service", + Bucket: &api.Ref{}, + Repository: &api.Repository{ + Kind: "git", + URL: "https://git-acme.local/customers.git", + Branch: "", + Tag: "", + Path: "", + }, + Binary: "corp.acme.demo:customers-tomcat:0.0.1-SNAPSHOT:war", + Tags: []api.TagRef{ + { + Name: "Oracle", + Source: "", + }, + { + Name: "Java", + Source: "", + }, + { + Name: "RHEL 8", + Source: "", + }, + { + Name: "Tomcat", + Source: "", + }, + }, + BusinessService: &api.Ref{ + Name: "Retail", + }, + }, + { + Name: "Inventory", + Description: "Inventory service", + Bucket: &api.Ref{}, + Repository: &api.Repository{ + Kind: "git", + URL: "https://git-acme.local/inventory.git", + Branch: "", + Tag: "", + Path: "", + }, + Binary: "corp.acme.demo:inventory:0.1.1-SNAPSHOT:war", + Tags: []api.TagRef{ + { + Name: "PostgreSQL", + Source: "", + }, + { + Name: "Java", + Source: "", + }, + { + Name: "RHEL 8", + Source: "", + }, + { + Name: "Quarkus", + Source: "", + }, + }, + BusinessService: &api.Ref{ + Name: "Retail", + }, + }, + { + Name: "Gateway", + Description: "API Gateway", + Bucket: &api.Ref{}, + Repository: &api.Repository{ + Kind: "git", + URL: "https://git-acme.local/gateway.git", + Branch: "", + Tag: "", + Path: "", + }, + Binary: "corp.acme.demo:gateway:0.1.1-SNAPSHOT:war", + Tags: []api.TagRef{ + { + Name: "Java", + Source: "", + }, + { + Name: "RHEL 8", + Source: "", + }, + { + Name: "Spring Boot", + Source: "", + }, + }, + BusinessService: &api.Ref{ + Name: "Retail", + }, + }, + }, + ExpectedDependencies: []api.Dependency{ + { + To: api.Ref{ + Name: "Inventory", + }, + From: api.Ref{ + Name: "Gateway", + }, + }, + { + To: api.Ref{ + Name: "Customers", + }, + From: api.Ref{ + Name: "Gateway", + }, + }, + }, + }, + } +) diff --git a/test/api/importcsv/template_application_import.csv b/test/api/importcsv/template_application_import.csv new file mode 100644 index 000000000..80f64fdab --- /dev/null +++ b/test/api/importcsv/template_application_import.csv @@ -0,0 +1,6 @@ +Record Type 1,Application Name,Description,Comments,Business Service,Dependency,Dependency Direction,Binary Group,Binary Artifact,Binary Version,Binary Packaging,Repository Type,Repository URL,Repository Branch,Repository Path,Tag Category 1,Tag 1,Tag Category 2,Tag 2,Tag Category 3,Tag 3,Tag Category 4,Tag 4,Tag Category 5,Tag 5,Tag Category 6,Tag 6,Tag Category 7,Tag 7,Tag Category 8,Tag 8,Tag Category 9,Tag 9,Tag Category 10,Tag 10,Tag Category 11,Tag 11,Tag Category 12,Tag 12,Tag Category 13,Tag 13,Tag Category 14,Tag 14,Tag Category 15,Tag 15,Tag Category 16,Tag 16,Tag Category 17,Tag 17,Tag Category 18,Tag 18,Tag Category 19,Tag 19,Tag Category 20,Tag 20 +1,Customers,Legacy Customers management service,,Retail,,,corp.acme.demo,customers-tomcat,0.0.1-SNAPSHOT,war,git,https://git-acme.local/customers.git,,,Operating System,RHEL 8,Database,Oracle,Language,Java,Runtime,Tomcat,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,Inventory,Inventory service,,Retail,,,corp.acme.demo,inventory,0.1.1-SNAPSHOT,war,git,https://git-acme.local/inventory.git,,,Operating System,RHEL 8,Database,Postgresql,Language,Java,Runtime,Quarkus,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,Gateway,API Gateway,,Retail,,,corp.acme.demo,gateway,0.1.1-SNAPSHOT,war,git,https://git-acme.local/gateway.git,,,Operating System,RHEL 8,,,Language,Java,Runtime,Spring Boot,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +2,Gateway,,,,Inventory,southbound +2,Gateway,,,,Customers,southbound