diff --git a/cmd/gctsExecuteABAPQualityChecks.go b/cmd/gctsExecuteABAPQualityChecks.go new file mode 100644 index 000000000..f971eb8f0 --- /dev/null +++ b/cmd/gctsExecuteABAPQualityChecks.go @@ -0,0 +1,1896 @@ +package cmd + +import ( + "bytes" + "encoding/xml" + "fmt" + "html" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/SAP/jenkins-library/pkg/command" + piperhttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/pkg/errors" +) + +var atcFailure, aUnitFailure bool + +func gctsExecuteABAPQualityChecks(config gctsExecuteABAPQualityChecksOptions, telemetryData *telemetry.CustomData) { + + // for command execution use Command + c := command.Command{} + // reroute command output to logging framework + c.Stdout(log.Writer()) + c.Stderr(log.Writer()) + + httpClient := &piperhttp.Client{} + // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end + err := rungctsExecuteABAPQualityChecks(&config, httpClient) + if err != nil { + log.Entry().WithError(err).Fatal("step execution failed") + } + + if aUnitFailure || atcFailure { + + log.Entry().Fatal("step execution failed") + + } + +} + +func rungctsExecuteABAPQualityChecks(config *gctsExecuteABAPQualityChecksOptions, httpClient piperhttp.Sender) error { + + const localChangedObjects = "localchangedobjects" + const remoteChangedObjects = "remotechangedobjects" + const localChangedPackages = "localchangedpackages" + const remoteChangedPackages = "remotechangedpackages" + const repository = "repository" + const packages = "packages" + + cookieJar, cookieErr := cookiejar.New(nil) + if cookieErr != nil { + return errors.Wrap(cookieErr, "creating a cookie jar failed") + } + + maxRetries := -1 + clientOptions := piperhttp.ClientOptions{ + CookieJar: cookieJar, + Username: config.Username, + Password: config.Password, + MaxRetries: maxRetries, + } + + httpClient.SetOptions(clientOptions) + + log.Entry().Infof("start of gctsExecuteABAPQualityChecks step with configuration values: %v", config) + + var objects []repoObject + var err error + + log.Entry().Info("scope:", config.Scope) + + switch strings.ToLower(config.Scope) { + case localChangedObjects: + objects, err = getLocalObjects(config, httpClient) + case remoteChangedObjects: + objects, err = getRemoteObjects(config, httpClient) + case localChangedPackages: + objects, err = getLocalPackages(config, httpClient) + case remoteChangedPackages: + objects, err = getRemotePackages(config, httpClient) + case repository: + objects, err = getRepositoryObjects(config, httpClient) + case packages: + objects, err = getPackages(config, httpClient) + default: + log.Entry().Info("the specified scope does not exists, the default one will be used:" + repository) + objects, err = getRepositoryObjects(config, httpClient) + } + + if err != nil { + log.Entry().WithError(err).Fatal("failure in get objects") + } + + if objects == nil { + log.Entry().Warning("no object delta was found, therefore the step execution will stop") + return nil + + } + + log.Entry().Infof("objects to be checked:") + for _, object := range objects { + log.Entry().Info(object.Type, " ", object.Object) + } + + if config.AUnitTest { + + // wrapper for execution of AUnit Test + err := executeAUnitTest(config, httpClient, objects) + + if err != nil { + log.Entry().WithError(err) + + } + + if aUnitFailure { + + log.Entry().Error("unit test(s) has/have failed! Check " + config.AUnitResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") + + } else { + + log.Entry().Info("AUnit test run completed successfully. If there are any results from the run, the results are saved in " + config.AUnitResultsFileName) + + } + } + + if config.AtcCheck { + + // wrapper for execution of ATCChecks + err = executeATCCheck(config, httpClient, objects) + + if err != nil { + log.Entry().WithError(err).Fatal("execute ATC Check failed") + } + + if atcFailure { + + log.Entry().Error(" ATC issue(s) found! Check " + config.AtcResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") + + } else { + + log.Entry().Info("ATCCheck test run completed successfully. If there are any results from the run, the results are saved in " + config.AtcResultsFileName) + + } + + } + + return nil + +} + +func getLocalObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + var localObjects []repoObject + var localObject repoObject + + log.Entry().Info("get local changed objects started") + + if config.Commit == "" { + + return []repoObject{}, errors.Errorf("For scope: localChangedObjects you need to specify a commit") + + } + + history, err := getHistory(config, client) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get local changed objects failed") + } + + if len(history.Result) == 0 { + + return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found") + } + + fromCommit := history.Result[0].FromCommit + log.Entry().Info("from Commit: ", fromCommit) + toCommit := history.Result[0].ToCommit + log.Entry().Info("to Commit: ", toCommit) + + // object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS + resp, err := getObjectDifference(config, fromCommit, toCommit, client) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get local changed objects failed") + } + + for _, object := range resp.Objects { + localObject.Object = object.Name + localObject.Type = object.Type + localObjects = append(localObjects, localObject) + } + + log.Entry().Info("get local changed objects finished") + + return localObjects, nil +} + +func getRemoteObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + var remoteObjects []repoObject + var remoteObject repoObject + var currentRemoteCommit string + + log.Entry().Info("get remote changed objects started") + + if config.Commit == "" { + + return []repoObject{}, errors.Errorf("For scope: remoteChangedObjects you need to specify a commit") + + } + + commitList, err := getCommitList(config, client) + + if err != nil { + return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") + } + + for i, commit := range commitList.Commits { + if commit.ID == config.Commit { + currentRemoteCommit = commitList.Commits[i+1].ID + break + } + } + if currentRemoteCommit == "" { + return []repoObject{}, errors.New("current remote commit was not found") + + } + log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) + // object delta between the commit that triggered the pipeline and the current commit in the remote repository + resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) + + if err != nil { + return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") + } + + for _, object := range resp.Objects { + remoteObject.Object = object.Name + remoteObject.Type = object.Type + remoteObjects = append(remoteObjects, remoteObject) + } + + log.Entry().Info("get remote changed objects finished") + + return remoteObjects, nil +} + +func getLocalPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + var localPackages []repoObject + var localPackage repoObject + + log.Entry().Info("get local changed packages started") + + if config.Commit == "" { + + return []repoObject{}, errors.Errorf("For scope: localChangedPackages you need to specify a commit") + + } + + history, err := getHistory(config, client) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get local changed objects failed") + } + + if len(history.Result) == 0 { + + return []repoObject{}, errors.Wrap(err, "no activities (from commit - to commit) were found") + } + + fromCommit := history.Result[0].FromCommit + log.Entry().Info("from Commit: ", fromCommit) + toCommit := history.Result[0].ToCommit + log.Entry().Info("to Commit: ", toCommit) + + // object delta between FromCommit and ToCommit retrieved from Activities Tab in gCTS + resp, err := getObjectDifference(config, fromCommit, config.Commit, client) + + if err != nil { + return []repoObject{}, errors.Wrap(err, "get local changed packages failed") + + } + + myPackages := map[string]bool{} + + // objects are resolved into packages(DEVC) + for _, object := range resp.Objects { + objInfo, err := getObjectInfo(config, client, object.Name, object.Type) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get local changed packages failed") + } + if myPackages[objInfo.Devclass] { + + } else { + myPackages[objInfo.Devclass] = true + localPackage.Object = objInfo.Devclass + localPackage.Type = "DEVC" + localPackages = append(localPackages, localPackage) + } + + } + + log.Entry().Info("get local changed packages finished") + return localPackages, nil +} + +func getRemotePackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + var remotePackages []repoObject + var remotePackage repoObject + var currentRemoteCommit string + + log.Entry().Info("get remote changed packages started") + + if config.Commit == "" { + + return []repoObject{}, errors.Errorf("For scope: remoteChangedPackages you need to specify a commit") + + } + + commitList, err := getCommitList(config, client) + + if err != nil { + return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") + } + + for i, commit := range commitList.Commits { + if commit.ID == config.Commit { + currentRemoteCommit = commitList.Commits[i+1].ID + break + } + } + + if currentRemoteCommit == "" { + return []repoObject{}, errors.Wrap(err, "current remote commit was not found") + + } + log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) + //object delta between the commit that triggered the pipeline and the current commit in the remote repository + resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") + } + + myPackages := map[string]bool{} + // objects are resolved into packages(DEVC) + for _, object := range resp.Objects { + objInfo, err := getObjectInfo(config, client, object.Name, object.Type) + if err != nil { + return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") + } + if myPackages[objInfo.Devclass] { + + } else { + myPackages[objInfo.Devclass] = true + remotePackage.Object = objInfo.Devclass + remotePackage.Type = "DEVC" + remotePackages = append(remotePackages, remotePackage) + } + + } + log.Entry().Info("get remote changed packages finished") + return remotePackages, nil +} + +func getRepositoryObjects(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + log.Entry().Info("get repository objects started") + + var repoResp repoObjectResponse + + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "/objects?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return []repoObject{}, errors.Wrap(httpErr, "could not get repository objects") + } else if resp == nil { + return []repoObject{}, errors.New("could not get repository objects: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) + if parsingErr != nil { + return []repoObject{}, errors.Errorf("%v", parsingErr) + } + + var repositoryObjects []repoObject + + // remove object type DEVC, because it is already included in scope packages + // also if you run ATC Checks for DEVC together with other object types, ATC checks will run only for DEVC + for _, object := range repoResp.Objects { + + if object.Type != "DEVC" { + repositoryObjects = append(repositoryObjects, object) + } + + } + + log.Entry().Info("get repository objects finished") + + // all objects that are part of the local repository + return repositoryObjects, nil +} + +func getPackages(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) ([]repoObject, error) { + + var packages []repoObject + + log.Entry().Info("get packages started") + + var repoResp repoObjectResponse + + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "/objects?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return []repoObject{}, errors.Wrap(httpErr, "get packages failed: could not get repository objects") + } else if resp == nil { + return []repoObject{}, errors.New("get packages failed: could not get repository objects: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) + if parsingErr != nil { + return []repoObject{}, errors.Errorf("%v", parsingErr) + } + // chose only DEVC from repository objects + for _, object := range repoResp.Objects { + + if object.Type == "DEVC" { + packages = append(packages, object) + } + + } + + log.Entry().Info("get packages finished") + return packages, nil +} + +func discoverServer(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (*http.Header, error) { + + url := config.Host + + "/sap/bc/adt/core/discovery?sap-client=" + config.Client + + header := make(http.Header) + header.Add("Accept", "application/atomsvc+xml") + header.Add("x-csrf-token", "fetch") + header.Add("saml2", "disabled") + + disc, httpErr := client.SendRequest("GET", url, nil, header, nil) + + defer func() { + if disc != nil && disc.Body != nil { + disc.Body.Close() + } + }() + + if httpErr != nil { + return nil, errors.Wrap(httpErr, "discovery of the ABAP server failed") + } else if disc == nil || disc.Header == nil { + return nil, errors.New("discovery of the ABAP server failed: did not retrieve a HTTP response") + } + + return &disc.Header, nil +} + +func executeAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) error { + + log.Entry().Info("execute ABAP Unit Test started") + + var innerXml string + var result runResult + + for _, object := range objects { + + switch object.Type { + case "CLAS": + innerXml = innerXml + `` + case "DEVC": + innerXml = innerXml + `` + + } + + } + + var xmlBody = []byte(` + + + + + + + + + + + + + + ` + + innerXml + + ` + + + `) + + resp, err := runAUnitTest(config, client, xmlBody) + if err != nil { + return errors.Wrap(err, "execute of Aunit test has failed") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) + if parsingErr != nil { + log.Entry().Warning(parsingErr) + return nil + } + + parsedRes, err := parseUnitResult(config, client, &result) + + if err != nil { + log.Entry().Warning(err) + return nil + } + + log.Entry().Info("execute ABAP Unit Test finished.", parsedRes.Text) + + return nil +} + +func runAUnitTest(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte) (response *http.Response, err error) { + + log.Entry().Info("run ABAP Unit Test started") + url := config.Host + + "/sap/bc/adt/abapunit/testruns?sap-client=" + config.Client + + discHeader, discError := discoverServer(config, client) + + if discError != nil { + return response, errors.Wrap(discError, "run of unit tests failed") + } + + if discHeader.Get("X-Csrf-Token") == "" { + + return response, errors.Errorf("could not retrieve x-csrf-token from server") + } + + header := make(http.Header) + header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) + header.Add("Accept", "application/xml") + header.Add("Content-Type", "application/vnd.sap.adt.abapunit.testruns.result.v1+xml") + + response, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) + + if httpErr != nil { + return response, errors.Wrap(httpErr, "run of unit tests failed") + } else if response == nil { + return response, errors.New("run of unit tests failed: did not retrieve a HTTP response") + } + + log.Entry().Info("run ABAP Unit Test finished") + return response, nil +} + +func parseUnitResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, aUnitRunResult *runResult) (parsedResult checkstyle, err error) { + + log.Entry().Info("parse ABAP Unit Result started") + + var fileName string + var aUnitFile file + var aUnitError checkstyleError + + parsedResult.Version = "1.0" + + for _, program := range aUnitRunResult.Program { + + objectType := program.Type[0:4] + objectName := program.Name + + //syntax error in unit test or class + if program.Alerts.Alert.HasSyntaxErrors == "true" { + + aUnitFailure = true + aUnitError.Source = objectName + aUnitError.Severity = "error" + log.Entry().Info("severity: ", aUnitError.Severity) + aUnitError.Message = html.UnescapeString(program.Alerts.Alert.Title + " " + program.Alerts.Alert.Details.Detail.AttrText) + log.Entry().Info("message: ", aUnitError.Message) + aUnitError.Line, err = findLine(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName, objectType) + log.Entry().Error("line: ", aUnitError.Line) + if err != nil { + return parsedResult, errors.Wrap(err, "parse AUnit Result failed") + + } + fileName, err = getFileName(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName) + log.Entry().Error("file path: ", aUnitError.Line) + if err != nil { + return parsedResult, errors.Wrap(err, "parse AUnit Result failed") + + } + + aUnitFile.Error = append(aUnitFile.Error, aUnitError) + aUnitError = checkstyleError{} + log.Entry().Error("there is a syntax error", aUnitFile) + } + + for _, testClass := range program.TestClasses.TestClass { + + for _, testMethod := range testClass.TestMethods.TestMethod { + + aUnitError.Source = testClass.Name + "/" + testMethod.Name + + // unit test failure + if len(testMethod.Alerts.Alert) > 0 { + + for _, testalert := range testMethod.Alerts.Alert { + + switch testalert.Severity { + case "fatal": + log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity fatal") + aUnitFailure = true + aUnitError.Severity = "error" + case "critical": + log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity critical") + aUnitFailure = true + aUnitError.Severity = "error" + case "tolerable": + log.Entry().Warning("unit test " + aUnitError.Source + " has failed with severity warning") + aUnitError.Severity = "warning" + default: + aUnitError.Severity = "info" + + } + + //unit test message is spread in different elements + for _, detail := range testalert.Details.Detail { + aUnitError.Message = aUnitError.Message + " " + detail.AttrText + for _, subdetail := range detail.Details.Detail { + + aUnitError.Message = html.UnescapeString(aUnitError.Message + " " + subdetail.AttrText) + log.Entry().Info("message: ", aUnitError.Message) + } + + } + + aUnitError.Line, err = findLine(config, client, testalert.Stack.StackEntry.URI, objectName, objectType) + log.Entry().Info("line: ", aUnitError.Line) + if err != nil { + + log.Entry().Warning(err) + + } + + } + + aUnitFile.Error = append(aUnitFile.Error, aUnitError) + aUnitError = checkstyleError{} + + } else { + + log.Entry().Info("unit test:", aUnitError.Source, "- was successful") + + } + + } + + fileName, err = getFileName(config, client, testClass.URI, objectName) + if err != nil { + return parsedResult, errors.Wrap(err, "parse AUnit Result failed") + + } + } + + aUnitFile.Name, err = constructPath(config, client, fileName, objectName, objectType) + log.Entry().Error("file path: ", aUnitFile.Name) + if err != nil { + + return parsedResult, errors.Wrap(err, "parse AUnit Result failed") + } + parsedResult.File = append(parsedResult.File, aUnitFile) + aUnitFile = file{} + + } + + body, _ := xml.Marshal(parsedResult) + + writeErr := ioutil.WriteFile(config.AUnitResultsFileName, body, 0644) + + if writeErr != nil { + log.Entry().Error("file AUnitResults.xml could not be created") + return parsedResult, fmt.Errorf("handling unit test results failed: %w", writeErr) + } + + log.Entry().Info("parse ABAP Unit Result finished") + return parsedResult, nil + +} + +func executeATCCheck(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objects []repoObject) (error error) { + + log.Entry().Info("execute ATC Check started") + + var innerXml string + var result worklist + + for _, object := range objects { + + switch object.Type { + + case "CLAS": + innerXml = innerXml + `` + case "INTF": + innerXml = innerXml + `` + case "DEVC": + innerXml = innerXml + `` + case "FUGR": + innerXml = innerXml + `` + case "TABL": + innerXml = innerXml + `` + case "DTEL": + innerXml = innerXml + `` + case "DOMA": + innerXml = innerXml + `` + case "MSAG": + innerXml = innerXml + `` + case "PROG": + innerXml = innerXml + `` + default: + log.Entry().Warning("object Type " + object.Type + " is not supported!") + + } + + } + + var xmlBody = []byte(` + + + + ` + innerXml + + ` + + + `) + + worklist, err := getWorklist(config, client) + if err != nil { + return errors.Wrap(err, "execution of ATC Checks failed") + } + + err = startATCRun(config, client, xmlBody, worklist) + + if err != nil { + return errors.Wrap(err, "execution of ATC Checks failed") + } + + resp, err := getATCRun(config, client, worklist) + + if err != nil { + return errors.Wrap(err, "execution of ATC Checks failed") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) + if parsingErr != nil { + log.Entry().Warning(parsingErr) + return nil + } + + atcRes, err := parseATCCheckResult(config, client, &result) + + if err != nil { + log.Entry().Error(err) + return errors.Wrap(err, "execution of ATC Checks failed") + } + + log.Entry().Info("execute ATC Checks finished.", atcRes.Text) + + return nil + +} +func startATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, xml []byte, worklistID string) (err error) { + + log.Entry().Info("ATC Run started") + + discHeader, discError := discoverServer(config, client) + if discError != nil { + return errors.Wrap(discError, "start of ATC run failed") + } + + if discHeader.Get("X-Csrf-Token") == "" { + return errors.Errorf("could not retrieve x-csrf-token from server") + } + + header := make(http.Header) + header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) + header.Add("Accept", "application/xml") + + url := config.Host + + "/sap/bc/adt/atc/runs?worklistId=" + worklistID + "&sap-client=" + config.Client + + resp, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return errors.Wrap(httpErr, "start of ATC run failed") + } else if resp == nil { + return errors.New("start of ATC run failed: did not retrieve a HTTP response") + } + + log.Entry().Info("ATC Run finished") + + return nil + +} + +func getATCRun(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, worklistID string) (response *http.Response, err error) { + + log.Entry().Info("get ATC Run Results started") + + header := make(http.Header) + + url := config.Host + + "/sap/bc/adt/atc/worklists/" + worklistID + "?sap-client=" + config.Client + + header.Add("Accept", "application/atc.worklist.v1+xml") + + resp, httpErr := client.SendRequest("GET", url, nil, header, nil) + + if httpErr != nil { + return response, errors.Wrap(httpErr, "get ATC run failed") + } else if resp == nil { + return response, errors.New("get ATC run failed: did not retrieve a HTTP response") + } + log.Entry().Info("get ATC Run Results finished") + return resp, nil + +} + +func getWorklist(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (worklistID string, error error) { + + url := config.Host + + "/sap/bc/adt/atc/worklists?checkVariant=" + config.AtcVariant + "&sap-client=" + config.Client + discHeader, discError := discoverServer(config, client) + + if discError != nil { + return worklistID, errors.Wrap(discError, "get worklist failed") + } + + if discHeader.Get("X-Csrf-Token") == "" { + return worklistID, errors.Errorf("could not retrieve x-csrf-token from server") + } + + header := make(http.Header) + header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) + header.Add("Accept", "*/*") + + resp, httpErr := client.SendRequest("POST", url, nil, header, nil) + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return worklistID, errors.Wrap(httpErr, "get worklist failed") + } else if resp == nil { + return worklistID, errors.New("get worklist failed: did not retrieve a HTTP response") + } + location := resp.Header["Location"][0] + locationSlice := strings.Split(location, "/") + worklistID = locationSlice[len(locationSlice)-1] + log.Entry().Info("worklist id for ATC check: ", worklistID) + + return worklistID, nil +} + +func parseATCCheckResult(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, response *worklist) (atcResults checkstyle, error error) { + + log.Entry().Info("parse ATC Check Result started") + + var atcFile file + var subObject string + var aTCUnitError checkstyleError + + atcResults.Version = "1.0" + + for _, object := range response.Objects.Object { + + objectType := object.Type + objectName := object.Name + + for _, atcworklist := range object.Findings.Finding { + + log.Entry().Info("there is atc finding for object type: ", objectType+" object name: "+objectName) + + path, err := url.PathUnescape(atcworklist.Location) + + if err != nil { + return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") + + } + + if len(atcworklist.Atcfinding) > 0 { + + priority, err := strconv.Atoi(atcworklist.Priority) + + if err != nil { + return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") + + } + + switch priority { + case 1: + atcFailure = true + aTCUnitError.Severity = "error" + log.Entry().Error("atc issue with priority: 1 ") + case 2: + atcFailure = true + aTCUnitError.Severity = "error" + log.Entry().Error("atc issue with priority: 2 ") + case 3: + aTCUnitError.Severity = "warning" + log.Entry().Warning("atc issue with priority: 3 ") + default: + aTCUnitError.Severity = "info" + log.Entry().Info("atc issue with low priority ") + } + + log.Entry().Error("severity: ", aTCUnitError.Severity) + + if aTCUnitError.Line == "" { + + aTCUnitError.Line, err = findLine(config, client, path, objectName, objectType) + log.Entry().Info("line: ", aTCUnitError.Line) + + if err != nil { + log.Entry().Info(path) + log.Entry().Warning(err) + + } + + } + + if subObject != "" { + aTCUnitError.Source = objectName + "/" + strings.ToUpper(subObject) + } else { + aTCUnitError.Source = objectName + } + + aTCUnitError.Message = html.UnescapeString(atcworklist.CheckTitle + " " + atcworklist.MessageTitle) + log.Entry().Info("message: ", aTCUnitError.Message) + atcFile.Error = append(atcFile.Error, aTCUnitError) + aTCUnitError = checkstyleError{} + } + + if atcFile.Error[0].Message != "" { + + fileName, err := getFileName(config, client, path, objectName) + + if err != nil { + return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") + } + + atcFile.Name, err = constructPath(config, client, fileName, objectName, objectType) + log.Entry().Info("file path: ", atcFile.Name) + if err != nil { + return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") + } + atcResults.File = append(atcResults.File, atcFile) + atcFile = file{} + + } + + } + } + + atcBody, _ := xml.Marshal(atcResults) + + writeErr := ioutil.WriteFile(config.AtcResultsFileName, atcBody, 0644) + + if writeErr != nil { + log.Entry().Error("ATCResults.xml could not be created") + return atcResults, fmt.Errorf("handling atc results failed: %w", writeErr) + } + log.Entry().Info("parsing ATC check results to CheckStyle has finished.") + return atcResults, writeErr +} + +func constructPath(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, fileName string, objectName string, objectType string) (filePath string, error error) { + + targetDir, err := getTargetDir(config, client) + if err != nil { + return filePath, errors.Wrap(err, "path could not be constructed") + + } + + filePath = config.Workspace + "/" + targetDir + "/objects/" + strings.ToUpper(objectType) + "/" + strings.ToUpper(objectName) + "/" + fileName + return filePath, nil + +} + +func findLine(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objectName string, objectType string) (line string, error error) { + + regexLine := regexp.MustCompile(`.start=\d*`) + regexMethod := regexp.MustCompile(`.name=[a-zA-Z0-9_-]*;`) + + readableSource, err := checkReadableSource(config, client) + + if err != nil { + + return line, errors.Wrap(err, "could not find line in source code") + + } + + fileName, err := getFileName(config, client, path, objectName) + + if err != nil { + + return line, err + + } + + filePath, err := constructPath(config, client, fileName, objectName, objectType) + if err != nil { + return line, errors.Wrap(err, objectType+"/"+objectName+"could not find line in source code") + + } + + var absLine int + if readableSource { + + // the error line that we get from UnitTest Run or ATC Check is not aligned for the readable source, we need to calculated it + rawfile, err := ioutil.ReadFile(filePath) + + if err != nil { + + return line, errors.Wrapf(err, "could not find object in the workspace of your CI/CD tool ") + } + + file := string(rawfile) + + splittedfile := strings.Split(file, "\n") + + // CLAS/OSO - is unique identifier for protection section in CLAS + if strings.Contains(path, "CLAS/OSO") { + + for l, line := range splittedfile { + + if strings.Contains(line, "protected section.") { + absLine = l + break + } + + } + + // CLAS/OM - is unique identifier for method section in CLAS + } else if strings.Contains(path, "CLAS/OM") { + + methodName := regexMethod.FindString(path) + + if methodName != "" { + methodName = methodName[len(`.name=`) : len(methodName)-1] + + } + + for line, linecontent := range splittedfile { + + if strings.Contains(linecontent, "method"+" "+methodName) { + absLine = line + break + } + + } + + // CLAS/OSI - is unique identifier for private section in CLAS + } else if strings.Contains(path, "CLAS/OSI") { + + for line, linecontent := range splittedfile { + + if strings.Contains(linecontent, "private section.") { + absLine = line + break + } + + } + + } + + errLine := regexLine.FindString(path) + + if errLine != "" { + + errLine, err := strconv.Atoi(errLine[len(`.start=`):]) + if err == nil { + line = strconv.Itoa(absLine + errLine) + + } + + } + + } else { + // classic format + errLine := regexLine.FindString(path) + if errLine != "" { + line = errLine[len(`.start=`):] + + } + + } + + return line, nil +} +func getFileName(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, path string, objName string) (fileName string, error error) { + + readableSource, err := checkReadableSource(config, client) + if err != nil { + return fileName, errors.Wrap(err, "get file name has failed") + + } + + path, err = url.PathUnescape(path) + + var fileExtension string + fileExtensionLength := 30 - len(objName) + for i := 0; i < fileExtensionLength; i++ { + fileExtension += "=" + } + + if err != nil { + return fileName, errors.Wrap(err, "get file name has failed") + + } + + // INTERFACES + regexInterface := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/interfaces\/\w*`) + intf := regexInterface.FindString(path) + if intf != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".intf.abap" + } else { + fileName = "REPS " + strings.ToUpper(objName) + fileExtension + "IU.abap" + } + + } + // CLASSES DEFINITIONS + regexClasDef := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/definitions\/`) + clasDef := regexClasDef.FindString(path) + if clasDef != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.definitions.abap" + } else { + fileName = "CINC " + objName + fileExtension + "CCDEF.abap" + } + + } + + // CLASSES IMPLEMENTATIONS + regexClasImpl := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/implementations\/`) + clasImpl := regexClasImpl.FindString(path) + if clasImpl != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.implementations.abap" + } else { + fileName = "CINC " + objName + fileExtension + "CCIMP.abap" + } + + } + + // CLASSES MACROS + regexClasMacro := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/macros\/`) + clasMacro := regexClasMacro.FindString(path) + if clasMacro != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.macros.abap" + } else { + fileName = "CINC " + objName + fileExtension + "CCMAC.abap" + } + + } + + // TEST CLASSES + regexTestClass := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*#?\/?\w*\/?testclass`) + testClass := regexTestClass.FindString(path) + if testClass != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.testclasses.abap" + } else { + fileName = "CINC " + objName + fileExtension + "CCAU.abap" + } + + } + + // CLASS PROTECTED + regexClasProtected := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSO`) + classProtected := regexClasProtected.FindString(path) + if classProtected != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.abap" + } else { + fileName = "CPRO " + objName + ".abap" + } + + } + + // CLASS PRIVATE + regexClasPrivate := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSI`) + classPrivate := regexClasPrivate.FindString(path) + if classPrivate != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.abap" + } else { + fileName = "CPRI " + objName + ".abap" + } + + } + + // CLASS METHOD + regexClasMethod := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OM`) + classMethod := regexClasMethod.FindString(path) + if classMethod != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.abap" + } else { + + regexmethodName := regexp.MustCompile(`name=\w*`) + methodName := regexmethodName.FindString(path) + + fileName = "METH " + methodName[len(`name=`):] + ".abap" + } + + } + + // CLASS PUBLIC + regexClasPublic := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#start`) + classPublic := regexClasPublic.FindString(path) + if classPublic != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.abap" + } else { + fileName = "CPUB " + objName + ".abap" + } + + } + + // FUNCTION INCLUDE + regexFuncIncl := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/includes/\w*`) + + funcIncl := regexFuncIncl.FindString(path) + if funcIncl != "" && fileName == "" { + + regexSubObj := regexp.MustCompile(`includes\/\w*`) + subObject := regexSubObj.FindString(path) + subObject = subObject[len(`includes/`):] + + if readableSource { + + fileName = strings.ToLower(objName) + ".fugr." + strings.ToLower(subObject) + ".reps.abap" + } else { + fileName = "REPS " + strings.ToUpper(subObject) + ".abap" + } + + } + + // FUNCTION GROUP + regexFuncGr := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/source\/main`) + + funcGr := regexFuncGr.FindString(path) + if funcGr != "" && fileName == "" { + + if readableSource { + + fileName = strings.ToLower(objName) + ".fugr.sapl" + strings.ToLower(objName) + ".reps.abap" + } else { + fileName = "REPS SAPL" + objName + ".abap" + } + + } + + // FUNCTION MODULE + regexFuncMod := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/fmodules/\w*`) + funcMod := regexFuncMod.FindString(path) + if funcMod != "" && fileName == "" { + + regexSubObj := regexp.MustCompile(`includes\/\w*`) + subObject := regexSubObj.FindString(path) + subObject = subObject[len(`includes/`):] + + if readableSource { + + fileName = strings.ToLower(subObject) + ".func.abap" + } else { + fileName = "FUNC " + subObject + ".abap" + } + + } + // CLAS + regexClas := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/` + strings.ToLower(objName)) + clas := regexClas.FindString(path) + if clas != "" && fileName == "" { + if readableSource { + + fileName = strings.ToLower(objName) + ".clas.abap" + } else { + + fileName = "CPUB " + objName + ".abap" + } + + } + + // PROGRAM + regexProg := regexp.MustCompile(`\/sap\/bc\/adt\/programs\/programs\/` + strings.ToLower(objName)) + prog := regexProg.FindString(path) + if prog != "" && fileName == "" { + + fileName = "REPS " + objName + ".abap" + + } + + // TABLES + regexTab := regexp.MustCompile(`\/sap\/bc\/adt\/ddic\/tables\/` + strings.ToLower(objName)) + tab := regexTab.FindString(path) + if tab != "" && fileName == "" { + + fileName = "TABL " + objName + ".asx.json" + + } + + return fileName, nil + +} + +func getTargetDir(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (string, error) { + + var targetDir string + + repository, err := getRepo(config, client) + + if err != nil { + return targetDir, err + } + + for _, config := range repository.Result.Config { + if config.Key == "VCS_TARGET_DIR" { + targetDir = config.Value + } + } + + return targetDir, nil + +} + +func checkReadableSource(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (readableSource bool, error error) { + + repoLayout, err := getRepositoryLayout(config, client) + if err != nil { + return readableSource, errors.Wrap(err, "could not check readable source format") + } + + if repoLayout.Layout.ReadableSource == "true" || repoLayout.Layout.ReadableSource == "only" || repoLayout.Layout.ReadableSource == "all" { + + readableSource = true + + } else { + + readableSource = false + + } + + return readableSource, nil +} + +func getRepo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (repositoryResponse, error) { + + var repositoryResp repositoryResponse + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "?sap-client=" + config.Client + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return repositoryResponse{}, errors.Wrap(httpErr, "could not get repository") + } else if resp == nil { + return repositoryResponse{}, errors.New("could not get repository: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repositoryResp) + if parsingErr != nil { + return repositoryResponse{}, errors.Errorf("%v", parsingErr) + } + + return repositoryResp, nil + +} + +func getRepositoryLayout(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (layoutResponse, error) { + + var repoLayoutResponse layoutResponse + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "/layout?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return layoutResponse{}, errors.Wrap(httpErr, "could not get repository layout") + } else if resp == nil { + return layoutResponse{}, errors.New("could not get repository layout: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoLayoutResponse) + if parsingErr != nil { + return layoutResponse{}, errors.Errorf("%v", parsingErr) + } + + return repoLayoutResponse, nil +} + +func getCommitList(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (commitResponse, error) { + + var commitResp commitResponse + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "/getCommit?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return commitResponse{}, errors.Wrap(httpErr, "get repository history failed") + } else if resp == nil { + return commitResponse{}, errors.New("get repository history failed: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &commitResp) + if parsingErr != nil { + return commitResponse{}, errors.Errorf("%v", parsingErr) + } + + return commitResp, nil +} + +func getObjectDifference(config *gctsExecuteABAPQualityChecksOptions, fromCommit string, toCommit string, client piperhttp.Sender) (objectsResponse, error) { + var objectResponse objectsResponse + + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + + "/compareCommits?fromCommit=" + fromCommit + "&toCommit=" + toCommit + "&sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return objectsResponse{}, errors.Wrap(httpErr, "get object difference failed") + } else if resp == nil { + return objectsResponse{}, errors.New("get object difference failed: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectResponse) + if parsingErr != nil { + return objectsResponse{}, errors.Errorf("%v", parsingErr) + } + log.Entry().Info("get object differences: ", objectResponse.Objects) + return objectResponse, nil +} + +func getObjectInfo(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender, objectName string, objectType string) (objectInfo, error) { + + var objectMetInfoResponse objectInfo + url := config.Host + + "/sap/bc/cts_abapvcs/objects/" + objectType + "/" + objectName + + "?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + + if httpErr != nil { + return objectInfo{}, errors.Wrap(httpErr, "resolve package failed") + } else if resp == nil { + return objectInfo{}, errors.New("resolve package failed: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectMetInfoResponse) + if parsingErr != nil { + return objectInfo{}, errors.Errorf("%v", parsingErr) + } + return objectMetInfoResponse, nil + +} + +func getHistory(config *gctsExecuteABAPQualityChecksOptions, client piperhttp.Sender) (historyResponse, error) { + + var historyResp historyResponse + url := config.Host + + "/sap/bc/cts_abapvcs/repository/" + config.Repository + "/getHistory?sap-client=" + config.Client + + resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) + + defer func() { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + }() + if httpErr != nil { + return historyResponse{}, errors.Wrap(httpErr, "get history failed") + } else if resp == nil { + return historyResponse{}, errors.New("get history failed: did not retrieve a HTTP response") + } + + parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &historyResp) + if parsingErr != nil { + return historyResponse{}, errors.Errorf("%v", parsingErr) + } + + return historyResp, nil +} + +type worklist struct { + XMLName xml.Name `xml:"worklist"` + Text string `xml:",chardata"` + ID string `xml:"id,attr"` + Timestamp string `xml:"timestamp,attr"` + UsedObjectSet string `xml:"usedObjectSet,attr"` + ObjectSetIsComplete string `xml:"objectSetIsComplete,attr"` + Atcworklist string `xml:"atcworklist,attr"` + ObjectSets struct { + Text string `xml:",chardata"` + ObjectSet []struct { + Text string `xml:",chardata"` + Name string `xml:"name,attr"` + Title string `xml:"title,attr"` + Kind string `xml:"kind,attr"` + } `xml:"objectSet"` + } `xml:"objectSets"` + Objects struct { + Text string `xml:",chardata"` + Object []struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Type string `xml:"type,attr"` + Name string `xml:"name,attr"` + PackageName string `xml:"packageName,attr"` + Author string `xml:"author,attr"` + Atcobject string `xml:"atcobject,attr"` + Adtcore string `xml:"adtcore,attr"` + Findings struct { + Text string `xml:",chardata"` + Finding []struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Location string `xml:"location,attr"` + Processor string `xml:"processor,attr"` + LastChangedBy string `xml:"lastChangedBy,attr"` + Priority string `xml:"priority,attr"` + CheckId string `xml:"checkId,attr"` + CheckTitle string `xml:"checkTitle,attr"` + MessageId string `xml:"messageId,attr"` + MessageTitle string `xml:"messageTitle,attr"` + ExemptionApproval string `xml:"exemptionApproval,attr"` + ExemptionKind string `xml:"exemptionKind,attr"` + Checksum string `xml:"checksum,attr"` + QuickfixInfo string `xml:"quickfixInfo,attr"` + Atcfinding string `xml:"atcfinding,attr"` + Link struct { + Text string `xml:",chardata"` + Href string `xml:"href,attr"` + Rel string `xml:"rel,attr"` + Type string `xml:"type,attr"` + Atom string `xml:"atom,attr"` + } `xml:"link"` + Quickfixes struct { + Text string `xml:",chardata"` + Manual string `xml:"manual,attr"` + Automatic string `xml:"automatic,attr"` + Pseudo string `xml:"pseudo,attr"` + } `xml:"quickfixes"` + } `xml:"finding"` + } `xml:"findings"` + } `xml:"object"` + } `xml:"objects"` +} + +type runResult struct { + XMLName xml.Name `xml:"runResult"` + Text string `xml:",chardata"` + Aunit string `xml:"aunit,attr"` + Program []struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Type string `xml:"type,attr"` + Name string `xml:"name,attr"` + URIType string `xml:"uriType,attr"` + Adtcore string `xml:"adtcore,attr"` + Alerts struct { + Text string `xml:",chardata"` + Alert struct { + Text string `xml:",chardata"` + HasSyntaxErrors string `xml:"hasSyntaxErrors,attr"` + Kind string `xml:"kind,attr"` + Severity string `xml:"severity,attr"` + Title string `xml:"title"` + Details struct { + Text string `xml:",chardata"` + Detail struct { + Text string `xml:",chardata"` + AttrText string `xml:"text,attr"` + } `xml:"detail"` + } `xml:"details"` + Stack struct { + Text string `xml:",chardata"` + StackEntry struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Description string `xml:"description,attr"` + } `xml:"stackEntry"` + } `xml:"stack"` + } `xml:"alert"` + } `xml:"alerts"` + + TestClasses struct { + Text string `xml:",chardata"` + TestClass []struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Type string `xml:"type,attr"` + Name string `xml:"name,attr"` + URIType string `xml:"uriType,attr"` + NavigationURI string `xml:"navigationUri,attr"` + DurationCategory string `xml:"durationCategory,attr"` + RiskLevel string `xml:"riskLevel,attr"` + TestMethods struct { + Text string `xml:",chardata"` + TestMethod []struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Type string `xml:"type,attr"` + Name string `xml:"name,attr"` + ExecutionTime string `xml:"executionTime,attr"` + URIType string `xml:"uriType,attr"` + NavigationURI string `xml:"navigationUri,attr"` + Unit string `xml:"unit,attr"` + Alerts struct { + Text string `xml:",chardata"` + Alert []struct { + Text string `xml:",chardata"` + Kind string `xml:"kind,attr"` + Severity string `xml:"severity,attr"` + Title string `xml:"title"` + Details struct { + Text string `xml:",chardata"` + Detail []struct { + Text string `xml:",chardata"` + AttrText string `xml:"text,attr"` + Details struct { + Text string `xml:",chardata"` + Detail []struct { + Text string `xml:",chardata"` + AttrText string `xml:"text,attr"` + } `xml:"detail"` + } `xml:"details"` + } `xml:"detail"` + } `xml:"details"` + Stack struct { + Text string `xml:",chardata"` + StackEntry struct { + Text string `xml:",chardata"` + URI string `xml:"uri,attr"` + Type string `xml:"type,attr"` + Name string `xml:"name,attr"` + Description string `xml:"description,attr"` + } `xml:"stackEntry"` + } `xml:"stack"` + } `xml:"alert"` + } `xml:"alerts"` + } `xml:"testMethod"` + } `xml:"testMethods"` + } `xml:"testClass"` + } `xml:"testClasses"` + } `xml:"program"` +} + +type gctsException struct { + Message string `json:"message"` + Description string `json:"description"` + Code int `json:"code"` +} + +type gctsLogs struct { + Time int `json:"time"` + User string `json:"user"` + Section string `json:"section"` + Action string `json:"action"` + Severity string `json:"severity"` + Message string `json:"message"` + Code string `json:"code"` +} + +type commit struct { + ID string `json:"id"` +} + +type commitResponse struct { + Commits []commit `json:"commits"` + ErrorLog []gctsLogs `json:"errorLog"` + Log []gctsLogs `json:"log"` + Exception gctsException `json:"exception"` +} + +type objectInfo struct { + Pgmid string `json:"pgmid"` + Object string `json:"object"` + ObjName string `json:"objName"` + Srcsystem string `json:"srcsystem"` + Author string `json:"author"` + Devclass string `json:"devclass"` +} + +type repoConfig struct { + Key string `json:"key"` + Value string `json:"value"` + Cprivate string `json:"cprivate"` + Cprotected string `json:"cprotected"` + Cvisible string `json:"cvisible"` + Category string `json:"category"` + Scope string `json:"scope"` + ChangedAt float64 `json:"changeAt"` + ChangedBy string `json:"changedBy"` +} + +type repository struct { + Rid string `json:"rid"` + Name string `json:"name"` + Role string `json:"role"` + Type string `json:"type"` + Vsid string `json:"vsid"` + PrivateFlag string `json:"privateFlag"` + Status string `json:"status"` + Branch string `json:"branch"` + Url string `json:"url"` + CreatedBy string `json:"createdBy"` + CreatedDate string `json:"createdDate"` + Config []repoConfig `json:"config"` + Objects int `json:"objects"` + CurrentCommit string `json:"currentCommit"` +} + +type repositoryResponse struct { + Result repository `json:"result"` + Exception gctsException `json:"exception"` +} + +type objects struct { + Name string `json:"name"` + Type string `json:"type"` + Action string `json:"action"` +} +type objectsResponse struct { + Objects []objects `json:"objects"` + Log []gctsLogs `json:"log"` + Exception gctsException `json:"exception"` + ErrorLogs []gctsLogs `json:"errorLog"` +} + +type repoObject struct { + Pgmid string `json:"pgmid"` + Object string `json:"object"` + Type string `json:"type"` + Description string `json:"description"` +} + +type repoObjectResponse struct { + Objects []repoObject `json:"objects"` + Log []gctsLogs `json:"log"` + Exception gctsException `json:"exception"` + ErrorLogs []gctsLogs `json:"errorLog"` +} + +type layout struct { + FormatVersion int `json:"formatVersion"` + Format string `json:"format"` + ObjectStorage string `json:"objectStorage"` + MetaInformation string `json:"metaInformation"` + TableContent string `json:"tableContent"` + Subdirectory string `json:"subdirectory"` + ReadableSource string `json:"readableSource"` + KeepClient string `json:"keepClient"` +} + +type layoutResponse struct { + Layout layout `json:"layout"` + Log []gctsLogs `json:"log"` + Exception string `json:"exception"` + ErrorLogs []gctsLogs `json:"errorLog"` +} + +type history struct { + Rid string `json:"rid"` + CheckoutTime int `json:"checkoutTime"` + FromCommit string `json:"fromCommit"` + ToCommit string `json:"toCommit"` + Caller string `json:"caller"` + Type string `json:"type"` +} + +type historyResponse struct { + Result []history `xml:"result"` + Exception string `json:"exception"` +} + +type checkstyleError struct { + Text string `xml:",chardata"` + Message string `xml:"message,attr"` + Source string `xml:"source,attr"` + Line string `xml:"line,attr"` + Severity string `xml:"severity,attr"` +} + +type file struct { + Text string `xml:",chardata"` + Name string `xml:"name,attr"` + Error []checkstyleError `xml:"error"` +} + +type checkstyle struct { + XMLName xml.Name `xml:"checkstyle"` + Text string `xml:",chardata"` + Version string `xml:"version,attr"` + File []file `xml:"file"` +} diff --git a/cmd/gctsExecuteABAPQualityChecks_generated.go b/cmd/gctsExecuteABAPQualityChecks_generated.go new file mode 100644 index 000000000..2d9ecfddd --- /dev/null +++ b/cmd/gctsExecuteABAPQualityChecks_generated.go @@ -0,0 +1,301 @@ +// Code generated by piper's step-generator. DO NOT EDIT. + +package cmd + +import ( + "fmt" + "os" + "time" + + "github.com/SAP/jenkins-library/pkg/config" + "github.com/SAP/jenkins-library/pkg/log" + "github.com/SAP/jenkins-library/pkg/splunk" + "github.com/SAP/jenkins-library/pkg/telemetry" + "github.com/SAP/jenkins-library/pkg/validation" + "github.com/spf13/cobra" +) + +type gctsExecuteABAPQualityChecksOptions struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Host string `json:"host,omitempty"` + Repository string `json:"repository,omitempty"` + Client string `json:"client,omitempty"` + AUnitTest bool `json:"aUnitTest,omitempty"` + AtcCheck bool `json:"atcCheck,omitempty"` + AtcVariant string `json:"atcVariant,omitempty"` + Scope string `json:"scope,omitempty"` + Commit string `json:"commit,omitempty"` + Workspace string `json:"workspace,omitempty"` + AtcResultsFileName string `json:"atcResultsFileName,omitempty"` + AUnitResultsFileName string `json:"aUnitResultsFileName,omitempty"` +} + +// GctsExecuteABAPQualityChecksCommand Runs ABAP unit tests and ATC (ABAP Test Cockpit) checks for a specified object scope. +func GctsExecuteABAPQualityChecksCommand() *cobra.Command { + const STEP_NAME = "gctsExecuteABAPQualityChecks" + + metadata := gctsExecuteABAPQualityChecksMetadata() + var stepConfig gctsExecuteABAPQualityChecksOptions + var startTime time.Time + var logCollector *log.CollectorHook + var splunkClient *splunk.Splunk + telemetryClient := &telemetry.Telemetry{} + + var createGctsExecuteABAPQualityChecksCmd = &cobra.Command{ + Use: STEP_NAME, + Short: "Runs ABAP unit tests and ATC (ABAP Test Cockpit) checks for a specified object scope.", + Long: `This step executes ABAP unit test and ATC checks for a specified scope of objects that exist in a local Git repository on an ABAP system. +Depending on your use case, you can specify a scope of objects for which you want to execute the checks. In addition, you can choose whether you want to execute only ABAP units tests, or only ATC checks, or both. +By default, both checks are executed. +The results of the checks are stored in a [Checkstyle](https://checkstyle.sourceforge.io/) format. With the help of the Jenkins [Warnings-Next-Generation](https://plugins.jenkins.io/warnings-ng/) Plugin), you can view the issues found, and navigate to the exact line of the source code where the issue occurred. +To make the findings visible in Jenkins interface, you will need to use step recordIssues. An example will be shown in the Example section. +
+You can use this step as of SAP S/4HANA 2020.`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + startTime = time.Now() + log.SetStepName(STEP_NAME) + log.SetVerbose(GeneralConfig.Verbose) + + GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens) + + path, _ := os.Getwd() + fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path} + log.RegisterHook(fatalHook) + + err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile) + if err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + log.RegisterSecret(stepConfig.Username) + log.RegisterSecret(stepConfig.Password) + + if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 { + sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID) + log.RegisterHook(&sentryHook) + } + + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunkClient = &splunk.Splunk{} + logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID} + log.RegisterHook(logCollector) + } + + validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages()) + if err != nil { + return err + } + if err = validation.ValidateStruct(stepConfig); err != nil { + log.SetErrorCategory(log.ErrorConfiguration) + return err + } + + return nil + }, + Run: func(_ *cobra.Command, _ []string) { + stepTelemetryData := telemetry.CustomData{} + stepTelemetryData.ErrorCode = "1" + handler := func() { + config.RemoveVaultSecretFiles() + stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds()) + stepTelemetryData.ErrorCategory = log.GetErrorCategory().String() + stepTelemetryData.PiperCommitHash = GitCommit + telemetryClient.SetData(&stepTelemetryData) + telemetryClient.Send() + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunkClient.Send(telemetryClient.GetData(), logCollector) + } + } + log.DeferExitHandler(handler) + defer handler() + telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME) + if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 { + splunkClient.Initialize(GeneralConfig.CorrelationID, + GeneralConfig.HookConfig.SplunkConfig.Dsn, + GeneralConfig.HookConfig.SplunkConfig.Token, + GeneralConfig.HookConfig.SplunkConfig.Index, + GeneralConfig.HookConfig.SplunkConfig.SendLogs) + } + gctsExecuteABAPQualityChecks(stepConfig, &stepTelemetryData) + stepTelemetryData.ErrorCode = "0" + log.Entry().Info("SUCCESS") + }, + } + + addGctsExecuteABAPQualityChecksFlags(createGctsExecuteABAPQualityChecksCmd, &stepConfig) + return createGctsExecuteABAPQualityChecksCmd +} + +func addGctsExecuteABAPQualityChecksFlags(cmd *cobra.Command, stepConfig *gctsExecuteABAPQualityChecksOptions) { + cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User that authenticates to the ABAP system. Note – Don´t provide this parameter directly. Either set it in the environment, or in the Jenkins credentials store, and provide the ID as value of the abapCredentialsId parameter.") + cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password of the ABAP user that authenticates to the ABAP system. Note – Don´t provide this parameter directly. Either set it in the environment, or in the Jenkins credentials store, and provide the ID as value of the abapCredentialsId parameter.") + cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Protocol and host of the ABAP system, including the port. Please provide in the format ://:. Supported protocols are http and https.") + cmd.Flags().StringVar(&stepConfig.Repository, "repository", os.Getenv("PIPER_repository"), "Name (ID) of the local repository on the ABAP system") + cmd.Flags().StringVar(&stepConfig.Client, "client", os.Getenv("PIPER_client"), "Client of the ABAP system in which you want to execute the checks") + cmd.Flags().BoolVar(&stepConfig.AUnitTest, "aUnitTest", true, "Indication whether you want to execute the unit test checks.") + cmd.Flags().BoolVar(&stepConfig.AtcCheck, "atcCheck", true, "Indication whether you want to execute the ATC checks.") + cmd.Flags().StringVar(&stepConfig.AtcVariant, "atcVariant", `DEFAULT_REMOTE_REF`, "Variant for ATC checks") + cmd.Flags().StringVar(&stepConfig.Scope, "scope", `repository`, "Scope of objects for which you want to execute the checks:\n\n * localChangedObjects - object delta derived from last activity in the local repository. The checks are executed for the individual objects.\n * remoteChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the remote repository. The checks are executed for the individual objects.\n * localChangedPackages - object delta derived from last activity in the local repository. All objects are resolved into packages. The checks are executed for the packages.\n * remoteChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the remote repository. All objects are resolved into packages. The checks are executed for the packages.\n * repository - all objects that are part of the local repository. The checks are executed for the individual objects. Packages (DEVC) are excluded. This is the default scope.\n * packages - all packages that are part of the local repository . The checks are executed for the packages.\n") + cmd.Flags().StringVar(&stepConfig.Commit, "commit", os.Getenv("PIPER_commit"), "ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes remoteChangedObjects and remoteChangedPackages secifying a commit it's mandatory.") + cmd.Flags().StringVar(&stepConfig.Workspace, "workspace", os.Getenv("PIPER_workspace"), "Absolute path to directory which contains the source code that your CI/CD tool checks out. For example in Jenkins, the workspace parameter is /var/jenkins_home/workspace//") + cmd.Flags().StringVar(&stepConfig.AtcResultsFileName, "atcResultsFileName", `ATCResults.xml`, "Specifies output file name for the results from the ATC checks") + cmd.Flags().StringVar(&stepConfig.AUnitResultsFileName, "aUnitResultsFileName", `AUnitResults.xml`, "Specifies output file name for the results from the AUnit tests") + + cmd.MarkFlagRequired("username") + cmd.MarkFlagRequired("password") + cmd.MarkFlagRequired("host") + cmd.MarkFlagRequired("repository") + cmd.MarkFlagRequired("client") + cmd.MarkFlagRequired("workspace") +} + +// retrieve step metadata +func gctsExecuteABAPQualityChecksMetadata() config.StepData { + var theMetaData = config.StepData{ + Metadata: config.StepMetadata{ + Name: "gctsExecuteABAPQualityChecks", + Aliases: []config.Alias{}, + Description: "Runs ABAP unit tests and ATC (ABAP Test Cockpit) checks for a specified object scope.", + }, + Spec: config.StepSpec{ + Inputs: config.StepInputs{ + Secrets: []config.StepSecrets{ + {Name: "abapCredentialsId", Description: "ID taken from the Jenkins credentials store containing user name and password of the user that authenticates to the ABAP system on which you want to execute the checks.", Type: "jenkins"}, + }, + Parameters: []config.StepParameters{ + { + Name: "username", + ResourceRef: []config.ResourceReference{ + { + Name: "abapCredentialsId", + Param: "username", + Type: "secret", + }, + }, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_username"), + }, + { + Name: "password", + ResourceRef: []config.ResourceReference{ + { + Name: "abapCredentialsId", + Param: "password", + Type: "secret", + }, + }, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_password"), + }, + { + Name: "host", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_host"), + }, + { + Name: "repository", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_repository"), + }, + { + Name: "client", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_client"), + }, + { + Name: "aUnitTest", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + Default: true, + }, + { + Name: "atcCheck", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + Default: true, + }, + { + Name: "atcVariant", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: `DEFAULT_REMOTE_REF`, + }, + { + Name: "scope", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: `repository`, + }, + { + Name: "commit", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_commit"), + }, + { + Name: "workspace", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: true, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_workspace"), + }, + { + Name: "atcResultsFileName", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: `ATCResults.xml`, + }, + { + Name: "aUnitResultsFileName", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: `AUnitResults.xml`, + }, + }, + }, + }, + } + return theMetaData +} diff --git a/cmd/gctsExecuteABAPQualityChecks_generated_test.go b/cmd/gctsExecuteABAPQualityChecks_generated_test.go new file mode 100644 index 000000000..4c69c96dc --- /dev/null +++ b/cmd/gctsExecuteABAPQualityChecks_generated_test.go @@ -0,0 +1,17 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGctsExecuteABAPQualityChecksCommand(t *testing.T) { + t.Parallel() + + testCmd := GctsExecuteABAPQualityChecksCommand() + + // only high level testing performed - details are tested in step generation procedure + assert.Equal(t, "gctsExecuteABAPQualityChecks", testCmd.Use, "command name incorrect") + +} diff --git a/cmd/gctsExecuteABAPQualityChecks_test.go b/cmd/gctsExecuteABAPQualityChecks_test.go new file mode 100644 index 000000000..fab814adb --- /dev/null +++ b/cmd/gctsExecuteABAPQualityChecks_test.go @@ -0,0 +1,1853 @@ +package cmd + +import ( + "bytes" + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "testing" + + piperhttp "github.com/SAP/jenkins-library/pkg/http" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestDiscoverServerSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Scope: "localChangedObjects", + Commit: "0123456789abcdefghijkl", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("discover server successful", func(t *testing.T) { + + httpClient := httpMockGcts{ + StatusCode: 200, + Header: map[string][]string{"x-csrf-token": {"ZegUEgfa50R7ZfGGxOtx2A=="}}, + ResponseBody: ` + + + `} + + header, err := discoverServer(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/adt/core/discovery?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check header", func(t *testing.T) { + assert.Equal(t, http.Header{"x-csrf-token": []string{"ZegUEgfa50R7ZfGGxOtx2A=="}}, *header) + }) + + } + + }) +} + +func TestDiscoverServerFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost3.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Scope: "localChangedObjects", + Commit: "0123456789abcdefghijkl", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("a http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 403, ResponseBody: ` + + + + Service cannot be reached + + + + +
+
+

403 Forbidden

+
+
+ + + + `} + + header, err := discoverServer(&config, &httpClient) + + t.Run("check error", func(t *testing.T) { + assert.EqualError(t, err, "discovery of the ABAP server failed: a http error occurred") + }) + + t.Run("check header", func(t *testing.T) { + assert.Equal(t, (*http.Header)(nil), header) + }) + + }) + + t.Run("discovery response is nil", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: ``} + + header, err := discoverServer(&config, &httpClient) + + t.Run("check error", func(t *testing.T) { + assert.EqualError(t, err, "discovery of the ABAP server failed: did not retrieve a HTTP response") + }) + + t.Run("check header", func(t *testing.T) { + assert.Equal(t, (*http.Header)(nil), header) + }) + + }) + + t.Run("discovery header is nil", func(t *testing.T) { + + httpClient := httpMockGctsT{ + StatusCode: 200, + Header: nil, + ResponseBody: ` + + + `} + + header, err := discoverServer(&config, &httpClient) + + t.Run("check error", func(t *testing.T) { + assert.EqualError(t, err, "discovery of the ABAP server failed: did not retrieve a HTTP response") + }) + + t.Run("check header", func(t *testing.T) { + assert.Equal(t, (*http.Header)(nil), header) + }) + + }) +} + +func TestGetLocalObjectsSuccess(t *testing.T) { + + t.Run("return multiple objects successfully", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + + Object: "ZCL_GCTS", + Type: "CLAS", + } + object2 := repoObject{ + + Object: "ZP_GCTS", + Type: "DEVC", + } + object3 := repoObject{ + + Object: "ZIF_GCTS_API", + Type: "INTF", + } + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + repoObjects = append(repoObjects, object2) + repoObjects = append(repoObjects, object3) + + objects, err := getLocalObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + var repoObjects []repoObject + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} + + objects, err := getLocalObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetLocalObjectsFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGcts{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getLocalObjects(&config, &httpClient) + + assert.EqualError(t, err, "get local changed objects failed: get history failed: a http error occurred") + }) +} + +func TestGetRemoteObjectsSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("return multiple objects successfully", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + + Object: "ZCL_GCTS", + Type: "CLAS", + } + object2 := repoObject{ + + Object: "ZP_GCTS", + Type: "DEVC", + } + object3 := repoObject{ + + Object: "ZIF_GCTS_API", + Type: "INTF", + } + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + repoObjects = append(repoObjects, object2) + repoObjects = append(repoObjects, object3) + + objects, err := getRemoteObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} + var repoObjects []repoObject + objects, err := getRemoteObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetRemoteObjectsFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGcts{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getRemoteObjects(&config, &httpClient) + + assert.EqualError(t, err, "get remote changed objects failed: get repository history failed: a http error occurred") + }) +} + +func TestGetLocalPackagesSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedPackages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("return multiple objects successfully", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + + Object: "SGCTS", + + Type: "DEVC", + } + object2 := repoObject{ + + Object: "SGCTS_2", + Type: "DEVC", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + repoObjects = append(repoObjects, object2) + + objects, err := getLocalPackages(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedObjects", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} + var repoObjects []repoObject + objects, err := getLocalPackages(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetLocalPackagesFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "localChangedPackages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getLocalPackages(&config, &httpClient) + + assert.EqualError(t, err, "get local changed objects failed: get history failed: a http error occurred") + }) +} + +func TestGetRemotePackagesSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedPackages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("return multiple objects successfully", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + + Object: "SGCTS", + + Type: "DEVC", + } + object2 := repoObject{ + + Object: "SGCTS_2", + Type: "DEVC", + } + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + repoObjects = append(repoObjects, object2) + + objects, err := getRemotePackages(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedPackages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} + var repoObjects []repoObject + objects, err := getRemoteObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetRemotePackagesFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "remoteChangedPackages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getRemotePackages(&config, &httpClient) + + assert.EqualError(t, err, "get remote changed packages failed: get repository history failed: a http error occurred") + }) +} + +func TestGetPackagesSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "packages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("return multiple objects successfully", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + + Pgmid: "R3TR", + Object: "ZP_GCTS", + Type: "DEVC", + Description: "Package(ABAP Objects)", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + + objects, err := getPackages(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "packages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + httpClient := httpMockGcts{StatusCode: 200, ResponseBody: `{}`} + var repoObjects []repoObject + objects, err := getPackages(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/objects?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetPackagesFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "packages", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getPackages(&config, &httpClient) + + assert.EqualError(t, err, "get packages failed: could not get repository objects: a http error occurred") + }) +} + +func TestGetRepositoryObjectsSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("return multiple objects successfully", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object1 := repoObject{ + Pgmid: "R3TR", + Object: "ZCL_GCTS", + Type: "CLAS", + Description: "Class (ABAP Objects)", + } + + object3 := repoObject{ + Pgmid: "R3TR", + Object: "ZIF_GCTS_API", + Type: "INTF", + Description: "Interface (ABAP Objects)", + } + var repoObjects []repoObject + repoObjects = append(repoObjects, object1) + repoObjects = append(repoObjects, object3) + + objects, err := getRepositoryObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + + } + + }) + + t.Run("no objects returned by http call", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo2", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} + var repoObjects []repoObject + objects, err := getRepositoryObjects(&config, &httpClient) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/objects?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check package objects", func(t *testing.T) { + assert.Equal(t, repoObjects, objects) + }) + } + + }) +} + +func TestGetRepositoryObjectsFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + } + + t.Run("http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` + { + "exception": "No relation between system and repository" + } + `} + + _, err := getRepositoryObjects(&config, &httpClient) + + assert.EqualError(t, err, "could not get repository objects: a http error occurred") + }) +} + +func TestExecuteAUnitTestSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + AUnitResultsFileName: "AUnitResults.xml", + } + + t.Run("all unit tests were successful", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object := repoObject{ + Pgmid: "R3TR", + Object: "ZCL_GCTS", + Type: "CLAS", + Description: "Clas Object", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object) + + err := executeAUnitTest(&config, &httpClient, repoObjects) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + } + }) + + t.Run("no unit tests found", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: ` + + + + + The task definition does not refer to any test + + + + `} + object := repoObject{ + Pgmid: "R3TR", + Object: "ZP_NON_EXISTANT", + Type: "CLAS", + Description: "Clas Object", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object) + err := executeAUnitTest(&config, &httpClient, repoObjects) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + } + }) +} + +func TestExecuteAUnitTestFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + AUnitResultsFileName: "AUnitResults.xml", + } + + var repoObjects []repoObject + + t.Run("a http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 403, ResponseBody: ` + CSRF token validation failed + `} + + header := make(http.Header) + header.Add("Accept", "application/atomsvc+xml") + header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") + header.Add("saml2", "disabled") + + err := executeAUnitTest(&config, &httpClient, repoObjects) + + assert.EqualError(t, err, "execute of Aunit test has failed: run of unit tests failed: discovery of the ABAP server failed: a http error occurred") + + }) +} + +func TestExecuteATCCheckSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "DEFAULT_REMOTE_REF", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + header := make(http.Header) + header.Add("Accept", "application/atomsvc+xml") + header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") + header.Add("saml2", "disabled") + + t.Run("atc checks were found", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + object := repoObject{ + Pgmid: "R3TR", + Object: "ZCL_GCTS", + Type: "CLAS", + Description: "Clas Object", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object) + + err := executeATCCheck(&config, &httpClient, repoObjects) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + } + }) + + t.Run("no ATC Checks were found", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "CUSTOM_REMOTE_REF", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + object := repoObject{ + Pgmid: "R3TR", + Object: "ZP_NON_EXISTANT", + Type: "CLAS", + Description: "Clas Object", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object) + err := executeATCCheck(&config, &httpClient, repoObjects) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/adt/atc/worklists/3E3D0764F95Z01ABCDHEF9C9F6B5C14P?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + } + }) +} + +func TestExecuteATCCheckFailure(t *testing.T) { + + object := repoObject{ + Pgmid: "R3TR", + Object: "ZP_PIPER", + Type: "CLAS", + Description: "Clas Object", + } + + var repoObjects []repoObject + repoObjects = append(repoObjects, object) + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + t.Run("a http error occurred", func(t *testing.T) { + + httpClient := httpMockGcts{StatusCode: 403, ResponseBody: ` + CSRF token validation failed + `} + + header := make(http.Header) + header.Add("Accept", "application/atomsvc+xml") + header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") + header.Add("saml2", "disabled") + + err := executeATCCheck(&config, &httpClient, repoObjects) + + assert.EqualError(t, err, "execution of ATC Checks failed: get worklist failed: discovery of the ABAP server failed: a http error occurred") + + }) +} + +func TestParseAUnitResultSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AUnitResultsFileName: "AUnitResults.xml", + } + + t.Run("unit test is successful", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + var xmlBody = []byte(` + + + + + + + + + + + + + `) + + var resp *runResult + xml.Unmarshal(xmlBody, &resp) + + parsedRes, err := parseUnitResult(&config, &httpClient, resp) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check file name", func(t *testing.T) { + assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS/CINC ZCL_GCTS======================CCAU.abap", parsedRes.File[0].Name) + }) + + } + }) + + t.Run("unit test failed", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + var xmlBody = []byte(` + + + + + + + + + Critical Assertion Error: 'Check: ASSERT_EQUALS' +
+ +
+ +
+
+ +
+ + + +
+
+
+
+
+
+
+
`) + + var resp *runResult + xml.Unmarshal(xmlBody, &resp) + + parsedRes, err := parseUnitResult(&config, &httpClient, resp) + + if assert.NoError(t, err) { + + t.Run("check url", func(t *testing.T) { + assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) + }) + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "GET", httpClient.Method) + }) + + t.Run("check file name", func(t *testing.T) { + assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS_PIPER_DEMO/CINC ZCL_GCTS_PIPER_DEMO===========CCAU.abap", parsedRes.File[0].Name) + }) + + t.Run("check line number", func(t *testing.T) { + assert.Equal(t, "21", parsedRes.File[0].Error[0].Line) + }) + + t.Run("check severity", func(t *testing.T) { + assert.Equal(t, "error", parsedRes.File[0].Error[0].Severity) + }) + + t.Run("check source", func(t *testing.T) { + assert.Equal(t, "LTCL_MASTER/CHECK", parsedRes.File[0].Error[0].Source) + }) + + t.Run("check message", func(t *testing.T) { + assert.Equal(t, " Different Values: Expected [Hello] Actual [Hello2] Test 'LTCL_MASTER->CHECK' in Main Program 'ZCL_GCTS_PIPER_DEMO===========CP'.", parsedRes.File[0].Error[0].Message) + }) + + } + }) + +} + +func TestParseAUnitResultFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "DEFAULT_REMOTE_REF", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AUnitResultsFileName: "AUnitResults.xml", + } + + t.Run("parser fails", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 403} + + var xmlBody = []byte(` + + + + + + + + + Critical Assertion Error: 'Check: ASSERT_EQUALS' +
+ +
+ +
+
+ +
+ + + +
+
+
+
+
+
+
+
`) + + var resp *runResult + xml.Unmarshal(xmlBody, &resp) + + parsedRes, err := parseUnitResult(&config, &httpClient, resp) + + if assert.Error(t, err) { + + t.Run("check method", func(t *testing.T) { + assert.Equal(t, "parse AUnit Result failed: get file name has failed: could not check readable source format: could not get repository layout: a http error occurred", err.Error()) + }) + + assert.NotEmpty(t, parsedRes, "results are not empty") + + } + }) + +} + +func TestParseATCCheckResultSuccess(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "DEFAULT_REMOTE_REF", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + t.Run("atc found", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 200} + + var xmlBody = []byte(` + + + + + + + + + + + + + + + + `) + + var resp *worklist + + xml.Unmarshal(xmlBody, &resp) + + parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) + + if assert.NoError(t, err) { + + t.Run("check file name", func(t *testing.T) { + assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS/CPRI ZCL_GCTS.abap", parsedRes.File[0].Name) + }) + + t.Run("check line number", func(t *testing.T) { + assert.Equal(t, "20", parsedRes.File[0].Error[0].Line) + }) + + t.Run("check severity", func(t *testing.T) { + assert.Equal(t, "error", parsedRes.File[0].Error[0].Severity) + }) + + t.Run("check source", func(t *testing.T) { + assert.Equal(t, "ZCL_GCTS", parsedRes.File[0].Error[0].Source) + }) + + t.Run("check message", func(t *testing.T) { + assert.Equal(t, "Package Check (Remote-enabled) Package Violation - Error", parsedRes.File[0].Error[0].Message) + }) + + assert.NotEmpty(t, parsedRes, "results are not empty") + + } + }) + + t.Run("no ATC Checks were found", func(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "DEFAULT_REMOTE_REF", + Scope: "repository", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + httpClient := httpMockGctsT{StatusCode: 200} + + var xmlBody = []byte(` + + + + + + + `) + var resp *worklist + xml.Unmarshal(xmlBody, &resp) + parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) + + if assert.NoError(t, err) { + + assert.Equal(t, parsedRes.Version, "1.0") + + } + }) + +} + +func TestParseATCCheckResultFailure(t *testing.T) { + + config := gctsExecuteABAPQualityChecksOptions{ + Host: "http://testHost.com:50000", + Client: "000", + Repository: "testRepo", + Username: "testUser", + Password: "testPassword", + Commit: "0123456789abcdefghijkl", + AtcVariant: "DEFAULT_REMOTE_REF", + Scope: "repsoitory", + Workspace: "/var/jenkins_home/workspace/myFirstPipeline", + AtcResultsFileName: "ATCResults.xml", + } + + t.Run("a http error occurred", func(t *testing.T) { + + httpClient := httpMockGctsT{StatusCode: 403} + + var xmlBody = []byte(` + + + + + + + + + + + + + + + + `) + + var resp *worklist + xml.Unmarshal(xmlBody, &resp) + parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) + + assert.EqualError(t, err, "conversion of ATC check results to CheckStyle has failed: get file name has failed: could not check readable source format: could not get repository layout: a http error occurred") + assert.NotEmpty(t, parsedRes) + }) + +} + +type httpMockGctsT struct { + Method string // is set during test execution + URL string // is set before test execution + Header map[string][]string // is set before test execution + ResponseBody string // is set before test execution + Options piperhttp.ClientOptions // is set during test + StatusCode int // is set during test +} + +func (c *httpMockGctsT) SetOptions(options piperhttp.ClientOptions) { + c.Options = options +} + +func (c *httpMockGctsT) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) { + + c.Method = method + c.URL = url + + switch url { + + case "http://testHost.com:50000/sap/bc/adt/core/discovery?sap-client=000": + + c.Header = map[string][]string{"X-Csrf-Token": {"ZegUEgfa50R7ZfGGxOtx2A=="}} + + c.ResponseBody = ` + + + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000": + + c.ResponseBody = ` + { + "result": { + + "rid": "testRepo", + "name": "testRepo", + "role": "PROVIDED", + "type": "GIT", + "vsid": "vSID", + "privateFlag": "false", + "url": "http://github.com/testRepo", + "createdBy": "testUser", + "createdDate": "02/02/2022", + "objects": 3, + "currentCommit": "xyz987654321" + } + } + ` + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2?sap-client=000": + + c.ResponseBody = ` + { + "result": { + + "rid": "testRepo2", + "name": "testRepo2", + "role": "PROVIDED", + "type": "GIT", + "vsid": "vSID", + "privateFlag": "false", + "url": "http://github.com/testRepo2", + "createdBy": "testUser", + "createdDate": "02/02/2022", + "objects": 3, + "currentCommit": "xyz987654321" + } + + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/getCommit?sap-client=000": + + c.ResponseBody = ` + { + "commits": [ + { + + "id": "0123456789abcdefghijkl" + + }, + { + + "id": "7845abaujztrw785" + }, + { + + "id": "45poiztr785423" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/getCommit?sap-client=000": + + c.ResponseBody = ` + { + "commits": [ + { + + "id": "0123456789abcdefghijkl" + + }, + { + + "id": "7845abaujztrw785" + }, + { + + "id": "45poiztr785423" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/getHistory?sap-client=000": + + c.ResponseBody = ` + { + "result": [ + { + + "rid": "testRepo", + "checkoutTime": 20220216233655, + "fromCommit": "xyz987654321", + "toCommit": "0123456789abcdefghijkl", + "caller": "USER", + "type": "PULL" + }, + { + "rid": "testRepo", + "checkoutTime": 20220216233788, + "fromCommit": "ghi98765432", + "toCommit": "xyz987654321", + "caller": "USER", + "type": "PULL" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/getHistory?sap-client=000": + + c.ResponseBody = ` + { + "result": [ + { + + "rid": "testRepo", + "checkoutTime": 20220216233655, + "fromCommit": "xyz987654321", + "toCommit": "0123456789abcdefghijkl", + "caller": "USER", + "type": "PULL" + }, + { + "rid": "testRepo", + "checkoutTime": 20220216233788, + "fromCommit": "ghi98765432", + "toCommit": "xyz987654321", + "caller": "USER", + "type": "PULL" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000": + + c.ResponseBody = ` + { + "objects": [ + { + + "name": "ZCL_GCTS", + "type": "CLAS", + "action": "Class (ABAP Objects)" + }, + { + + "name": "ZP_GCTS", + "type": "DEVC", + "action": "Package(ABAP Objects)" + }, + { + + "name": "ZIF_GCTS_API", + "type": "INTF", + "action": "Interface (ABAP Objects)" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000": + + c.ResponseBody = ` + { + "objects": [ + { + + "name": "ZCL_GCTS", + "type": "CLAS", + "action": "Class (ABAP Objects)" + }, + { + + "name": "ZP_GCTS", + "type": "DEVC", + "action": "Package(ABAP Objects)" + }, + { + + "name": "ZIF_GCTS_API", + "type": "INTF", + "action": "Interface (ABAP Objects)" + } + ] + } + ` + case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/CLAS/ZCL_GCTS?sap-client=000": + + c.ResponseBody = ` + + { + + "pgmid": "R3TR", + "object": "CLAS", + "objName": "ZCL_GCTS", + "srcsystem": "src", + "author": "HUGO", + "devclass": "SGCTS" + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000": + + c.ResponseBody = ` + + { + + "pgmid": "R3TR", + "object": "INTF", + "objName": "ZIF_GCTS_API", + "srcsystem": "src", + "author": "HUGO", + "devclass": "SGCTS_2" + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/DEVC/ZP_GCTS?sap-client=000": + + c.ResponseBody = ` + + { + + "pgmid": "R3TR", + "object": "DEVC", + "objName": "ZP_GCTS", + "srcsystem": "src", + "author": "HUGO", + "devclass": "SGCTS" + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/layout?sap-client=000": + + c.ResponseBody = + `{ + "layout": { + "formatVersion": 5, + "format": "json", + "objectStorage": "plain", + "metaInformation": ".gctsmetadata/", + "tableContent": "true", + "subdirectory": "src/", + "readableSource": "false" + } + } + ` + + case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000": + + c.ResponseBody = ` + { + "objects": [ + { + "pgmid": "R3TR", + "object": "ZCL_GCTS", + "type": "CLAS", + "description": "Class (ABAP Objects)" + }, + { + + "pgmid": "R3TR", + "object": "ZP_GCTS", + "type": "DEVC", + "description": "Package(ABAP Objects)" + }, + { + "pgmid": "R3TR", + "object": "ZIF_GCTS_API", + "type": "INTF", + "description": "Interface (ABAP Objects)" + } + ] + } + ` + + case "http://testHost.com:50000/sap/bc/adt/abapunit/testruns?sap-client=000": + + c.Header = map[string][]string{"Accept": {"application/xml"}} + c.Header = map[string][]string{"x-csrf-token": {"ZegUEgfa50R7ZfGGxOtx2A=="}} + c.Header = map[string][]string{"Content-Type": {"application/vnd.sap.adt.abapunit.testruns.result.v1+xml"}} + + c.ResponseBody = ` + + + + + + + + + + + + + ` + + case "http://testHost.com:50000/sap/bc/adt/atc/worklists?checkVariant=DEFAULT_REMOTE_REF&sap-client=000": + + c.Header = map[string][]string{"Location": {"/atc/worklists/worklistId/123Z076495C01ABCDEF9C9F6B257OD70"}} + + case "http://testHost.com:50000/sap/bc/adt/atc/worklists?checkVariant=CUSTOM_REMOTE_REF&sap-client=000": + + c.Header = map[string][]string{"Location": {"/atc/worklists/worklistId/3E3D0764F95Z01ABCDHEF9C9F6B5C14P"}} + + case "http://testHost.com:50000/sap/bc/adt/atc/runs?worklistId=123Z076495C01ABCDEF9C9F6B257OD70?sap-client=000": + + c.ResponseBody = + ` + + 123Z076495C01ABCDEF9C9F6B257OD70 + 2021-11-29T14:46:46Z + + + FINDING_STATS + 0,0,1 + + + ` + + case "http://testHost.com:50000/sap/bc/adt/atc/worklists/3E3D0764F95Z01ABCDHEF9C9F6B5C14P?sap-client=000": + c.ResponseBody = + ` + + + + + + + ` + + case "http://testHost.com:50000/sap/bc/adt/atc/worklists/123Z076495C01ABCDEF9C9F6B257OD70?sap-client=000": + c.ResponseBody = + ` + + + + + + + + + + + + + + + +` + } + + if r != nil { + _, err := ioutil.ReadAll(r) + + if err != nil { + return nil, err + } + } + + res := http.Response{ + StatusCode: c.StatusCode, + Header: c.Header, + Body: ioutil.NopCloser(bytes.NewReader([]byte(c.ResponseBody))), + } + + if c.StatusCode >= 400 { + return &res, errors.New("a http error occurred") + } + + return &res, nil +} diff --git a/cmd/gctsExecuteABAPUnitTests.go b/cmd/gctsExecuteABAPUnitTests.go index d24e10eb5..eb59609b3 100644 --- a/cmd/gctsExecuteABAPUnitTests.go +++ b/cmd/gctsExecuteABAPUnitTests.go @@ -1,1839 +1,12 @@ package cmd import ( - "bytes" - "encoding/xml" - "fmt" - "html" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/SAP/jenkins-library/pkg/command" - piperhttp "github.com/SAP/jenkins-library/pkg/http" - "github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/telemetry" - "github.com/pkg/errors" ) -var atcFailure, aUnitFailure bool - func gctsExecuteABAPUnitTests(config gctsExecuteABAPUnitTestsOptions, telemetryData *telemetry.CustomData) { - // for command execution use Command - c := command.Command{} - // reroute command output to logging framework - c.Stdout(log.Writer()) - c.Stderr(log.Writer()) - - httpClient := &piperhttp.Client{} - // error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end - err := runGctsExecuteABAPUnitTests(&config, httpClient) - if err != nil { - log.Entry().WithError(err).Fatal("step execution failed") - } - - if aUnitFailure || atcFailure { - - log.Entry().Fatal("step execution failed") - - } + var qualityChecksConfig gctsExecuteABAPQualityChecksOptions = gctsExecuteABAPQualityChecksOptions(config) -} - -func runGctsExecuteABAPUnitTests(config *gctsExecuteABAPUnitTestsOptions, httpClient piperhttp.Sender) error { - - const localChangedObjects = "localchangedobjects" - const remoteChangedObjects = "remotechangedobjects" - const localChangedPackages = "localchangedpackages" - const remoteChangedPackages = "remotechangedpackages" - const repository = "repository" - const packages = "packages" - - cookieJar, cookieErr := cookiejar.New(nil) - if cookieErr != nil { - return errors.Wrap(cookieErr, "creating a cookie jar failed") - } - - clientOptions := piperhttp.ClientOptions{ - CookieJar: cookieJar, - Username: config.Username, - Password: config.Password, - } - - httpClient.SetOptions(clientOptions) - - log.Entry().Infof("start of gCTSExecuteABAPUnitTests step with configuration values: %v", config) - - var objects []repoObject - var err error - - log.Entry().Info("scope:", config.Scope) - - switch strings.ToLower(config.Scope) { - case localChangedObjects: - objects, err = getLocalObjects(config, httpClient) - case remoteChangedObjects: - objects, err = getRemoteObjects(config, httpClient) - case localChangedPackages: - objects, err = getLocalPackages(config, httpClient) - case remoteChangedPackages: - objects, err = getRemotePackages(config, httpClient) - case repository: - objects, err = getRepositoryObjects(config, httpClient) - case packages: - objects, err = getPackages(config, httpClient) - default: - log.Entry().Info("the specified scope does not exists, the default one will be used:" + repository) - objects, err = getRepositoryObjects(config, httpClient) - } - - if err != nil { - log.Entry().WithError(err).Fatal("failure in get objects") - } - - if objects == nil { - log.Entry().Warning("no object delta was found, therefore the step execution will stop") - return nil - - } - - log.Entry().Infof("objects to be checked:") - for _, object := range objects { - log.Entry().Info(object.Type, " ", object.Object) - } - - if config.AUnitTest { - - // wrapper for execution of AUnit Test - err := executeAUnitTest(config, httpClient, objects) - - if err != nil { - log.Entry().WithError(err) - - } - - if aUnitFailure { - - log.Entry().Error("unit test(s) has/have failed! Check " + config.AUnitResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") - - } else { - - log.Entry().Info("AUnit test run completed successfully. If there are any results from the run, the results are saved in " + config.AUnitResultsFileName) - - } - } - - if config.AtcCheck { - - // wrapper for execution of ATCChecks - err = executeATCCheck(config, httpClient, objects) - - if err != nil { - log.Entry().WithError(err).Fatal("execute ATC Check failed") - } - - if atcFailure { - - log.Entry().Error(" ATC issue(s) found! Check " + config.AtcResultsFileName + " for more information! If you have enabled Warnings-Next-Generation Plugin, you can see the issues there!") - - } else { - - log.Entry().Info("ATCCheck test run completed successfully. If there are any results from the run, the results are saved in " + config.AtcResultsFileName) - - } - - } - - return nil - -} - -func getLocalObjects(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - var localObjects []repoObject - var localObject repoObject - - log.Entry().Info("get local changed objects started") - - if config.Commit == "" { - - return []repoObject{}, errors.Errorf("For scope: localChangedObjects you need to specify a commit") - - } - - repository, err := getRepo(config, client) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get local changed objects failed") - } - - currentLocalCommit := repository.Result.CurrentCommit - log.Entry().Info("current commit in the local repository: ", currentLocalCommit) - - // object delta between the commit that triggered the pipeline and the current commit in the local repository - resp, err := getObjectDifference(config, currentLocalCommit, config.Commit, client) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get local changed objects failed") - } - - for _, object := range resp.Objects { - localObject.Object = object.Name - localObject.Type = object.Type - localObjects = append(localObjects, localObject) - } - - log.Entry().Info("get local changed objects finished") - - return localObjects, nil -} - -func getRemoteObjects(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - var remoteObjects []repoObject - var remoteObject repoObject - var currentRemoteCommit string - - log.Entry().Info("get remote changed objects started") - - if config.Commit == "" { - - return []repoObject{}, errors.Errorf("For scope: remoteChangedObjects you need to specify a commit") - - } - - commitList, err := getCommitList(config, client) - - if err != nil { - return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") - } - - for i, commit := range commitList.Commits { - if commit.ID == config.Commit { - currentRemoteCommit = commitList.Commits[i+1].ID - break - } - } - if currentRemoteCommit == "" { - return []repoObject{}, errors.New("current remote commit was not found") - - } - log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) - // object delta between the commit that triggered the pipeline and the current commit in the remote repository - resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) - - if err != nil { - return []repoObject{}, errors.Wrap(err, "get remote changed objects failed") - } - - for _, object := range resp.Objects { - remoteObject.Object = object.Name - remoteObject.Type = object.Type - remoteObjects = append(remoteObjects, remoteObject) - } - - log.Entry().Info("get remote changed objects finished") - - return remoteObjects, nil -} - -func getLocalPackages(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - var localPackages []repoObject - var localPackage repoObject - - log.Entry().Info("get local changed packages started") - - if config.Commit == "" { - - return []repoObject{}, errors.Errorf("For scope: localChangedPackages you need to specify a commit") - - } - - repository, err := getRepo(config, client) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get local changed packages failed") - } - - currentLocalCommit := repository.Result.CurrentCommit - log.Entry().Info("current commit in the local repository: ", currentLocalCommit) - - //object delta between the commit that triggered the pipeline and the current commit in the local repository - resp, err := getObjectDifference(config, currentLocalCommit, config.Commit, client) - - if err != nil { - return []repoObject{}, errors.Wrap(err, "get local changed packages failed") - - } - - myPackages := map[string]bool{} - - // objects are resolved into packages(DEVC) - for _, object := range resp.Objects { - objInfo, err := getObjectInfo(config, client, object.Name, object.Type) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get local changed packages failed") - } - if myPackages[objInfo.Devclass] { - - } else { - myPackages[objInfo.Devclass] = true - localPackage.Object = objInfo.Devclass - localPackage.Type = "DEVC" - localPackages = append(localPackages, localPackage) - } - - } - - log.Entry().Info("get local changed packages finished") - return localPackages, nil -} - -func getRemotePackages(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - var remotePackages []repoObject - var remotePackage repoObject - var currentRemoteCommit string - - log.Entry().Info("get remote changed packages started") - - if config.Commit == "" { - - return []repoObject{}, errors.Errorf("For scope: remoteChangedPackages you need to specify a commit") - - } - - commitList, err := getCommitList(config, client) - - if err != nil { - return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") - } - - for i, commit := range commitList.Commits { - if commit.ID == config.Commit { - currentRemoteCommit = commitList.Commits[i+1].ID - break - } - } - - if currentRemoteCommit == "" { - return []repoObject{}, errors.Wrap(err, "current remote commit was not found") - - } - log.Entry().Info("current commit in the remote repository: ", currentRemoteCommit) - //object delta between the commit that triggered the pipeline and the current commit in the remote repository - resp, err := getObjectDifference(config, currentRemoteCommit, config.Commit, client) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") - } - - myPackages := map[string]bool{} - // objects are resolved into packages(DEVC) - for _, object := range resp.Objects { - objInfo, err := getObjectInfo(config, client, object.Name, object.Type) - if err != nil { - return []repoObject{}, errors.Wrap(err, "get remote changed packages failed") - } - if myPackages[objInfo.Devclass] { - - } else { - myPackages[objInfo.Devclass] = true - remotePackage.Object = objInfo.Devclass - remotePackage.Type = "DEVC" - remotePackages = append(remotePackages, remotePackage) - } - - } - log.Entry().Info("get remote changed packages finished") - return remotePackages, nil -} - -func getRepositoryObjects(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - log.Entry().Info("get repository objects started") - - var repoResp repoObjectResponse - - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "/objects?sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return []repoObject{}, errors.Wrap(httpErr, "could not get repository objects") - } else if resp == nil { - return []repoObject{}, errors.New("could not get repository objects: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) - if parsingErr != nil { - return []repoObject{}, errors.Errorf("%v", parsingErr) - } - - var repositoryObjects []repoObject - - // remove object type DEVC, because it is already included in scope packages - // also if you run ATC Checks for DEVC together with other object types, ATC checks will run only for DEVC - for _, object := range repoResp.Objects { - - if object.Type != "DEVC" { - repositoryObjects = append(repositoryObjects, object) - } - - } - - log.Entry().Info("get repository objects finished") - - // all objects that are part of the local repository - return repositoryObjects, nil -} - -func getPackages(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) ([]repoObject, error) { - - var packages []repoObject - - log.Entry().Info("get packages started") - - var repoResp repoObjectResponse - - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "/objects?sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return []repoObject{}, errors.Wrap(httpErr, "get packages failed: could not get repository objects") - } else if resp == nil { - return []repoObject{}, errors.New("get packages failed: could not get repository objects: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoResp) - if parsingErr != nil { - return []repoObject{}, errors.Errorf("%v", parsingErr) - } - // chose only DEVC from repository objects - for _, object := range repoResp.Objects { - - if object.Type == "DEVC" { - packages = append(packages, object) - } - - } - - log.Entry().Info("get packages finished") - return packages, nil -} - -func discoverServer(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (*http.Header, error) { - - url := config.Host + - "/sap/bc/adt/core/discovery?sap-client=" + config.Client - - header := make(http.Header) - header.Add("Accept", "application/atomsvc+xml") - header.Add("x-csrf-token", "fetch") - header.Add("saml2", "disabled") - - disc, httpErr := client.SendRequest("GET", url, nil, header, nil) - - defer func() { - if disc != nil && disc.Body != nil { - disc.Body.Close() - } - }() - - if httpErr != nil { - return nil, errors.Wrap(httpErr, "discovery of the ABAP server failed") - } else if disc == nil || disc.Header == nil { - return nil, errors.New("discovery of the ABAP server failed: did not retrieve a HTTP response") - } - - return &disc.Header, nil -} - -func executeAUnitTest(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, objects []repoObject) error { - - log.Entry().Info("execute ABAP Unit Test started") - - var innerXml string - var result runResult - - for _, object := range objects { - - switch object.Type { - case "CLAS": - innerXml = innerXml + `` - case "DEVC": - innerXml = innerXml + `` - - } - - } - - var xmlBody = []byte(` - - - - - - - - - - - - - - ` + - innerXml + - ` - - - `) - - resp, err := runAUnitTest(config, client, xmlBody) - if err != nil { - return errors.Wrap(err, "execute of Aunit test has failed") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) - if parsingErr != nil { - log.Entry().Warning(parsingErr) - return nil - } - - parsedRes, err := parseUnitResult(config, client, &result) - - if err != nil { - log.Entry().Warning(err) - return nil - } - - log.Entry().Info("execute ABAP Unit Test finished.", parsedRes.Text) - - return nil -} - -func runAUnitTest(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, xml []byte) (response *http.Response, err error) { - - log.Entry().Info("run ABAP Unit Test started") - url := config.Host + - "/sap/bc/adt/abapunit/testruns?sap-client=" + config.Client - - discHeader, discError := discoverServer(config, client) - - if discError != nil { - return response, errors.Wrap(discError, "run of unit tests failed") - } - - if discHeader.Get("X-Csrf-Token") == "" { - - return response, errors.Errorf("could not retrieve x-csrf-token from server") - } - - header := make(http.Header) - header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) - header.Add("Accept", "application/xml") - header.Add("Content-Type", "application/vnd.sap.adt.abapunit.testruns.result.v1+xml") - - response, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) - - if httpErr != nil { - return response, errors.Wrap(httpErr, "run of unit tests failed") - } else if response == nil { - return response, errors.New("run of unit tests failed: did not retrieve a HTTP response") - } - - log.Entry().Info("run ABAP Unit Test finished") - return response, nil -} - -func parseUnitResult(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, aUnitRunResult *runResult) (parsedResult checkstyle, err error) { - - log.Entry().Info("parse ABAP Unit Result started") - - var fileName string - var aUnitFile file - var aUnitError checkstyleError - - parsedResult.Version = "1.0" - - for _, program := range aUnitRunResult.Program { - - objectType := program.Type[0:4] - objectName := program.Name - - //syntax error in unit test or class - if program.Alerts.Alert.HasSyntaxErrors == "true" { - - aUnitFailure = true - aUnitError.Source = objectName - aUnitError.Severity = "error" - log.Entry().Info("severity: ", aUnitError.Severity) - aUnitError.Message = html.UnescapeString(program.Alerts.Alert.Title + " " + program.Alerts.Alert.Details.Detail.AttrText) - log.Entry().Info("message: ", aUnitError.Message) - aUnitError.Line, err = findLine(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName, objectType) - log.Entry().Error("line: ", aUnitError.Line) - if err != nil { - return parsedResult, errors.Wrap(err, "parse AUnit Result failed") - - } - fileName, err = getFileName(config, client, program.Alerts.Alert.Stack.StackEntry.URI, objectName) - log.Entry().Error("file path: ", aUnitError.Line) - if err != nil { - return parsedResult, errors.Wrap(err, "parse AUnit Result failed") - - } - - aUnitFile.Error = append(aUnitFile.Error, aUnitError) - aUnitError = checkstyleError{} - log.Entry().Error("there is a syntax error", aUnitFile) - } - - for _, testClass := range program.TestClasses.TestClass { - - for _, testMethod := range testClass.TestMethods.TestMethod { - - aUnitError.Source = testClass.Name + "/" + testMethod.Name - - // unit test failure - if len(testMethod.Alerts.Alert) > 0 { - - for _, testalert := range testMethod.Alerts.Alert { - - switch testalert.Severity { - case "fatal": - log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity fatal") - aUnitFailure = true - aUnitError.Severity = "error" - case "critical": - log.Entry().Error("unit test " + aUnitError.Source + " has failed with severity critical") - aUnitFailure = true - aUnitError.Severity = "error" - case "tolerable": - log.Entry().Warning("unit test " + aUnitError.Source + " has failed with severity warning") - aUnitError.Severity = "warning" - default: - aUnitError.Severity = "info" - - } - - //unit test message is spread in different elements - for _, detail := range testalert.Details.Detail { - aUnitError.Message = aUnitError.Message + " " + detail.AttrText - for _, subdetail := range detail.Details.Detail { - - aUnitError.Message = html.UnescapeString(aUnitError.Message + " " + subdetail.AttrText) - log.Entry().Info("message: ", aUnitError.Message) - } - - } - - aUnitError.Line, err = findLine(config, client, testalert.Stack.StackEntry.URI, objectName, objectType) - log.Entry().Info("line: ", aUnitError.Line) - if err != nil { - - log.Entry().Warning(err) - - } - - } - - aUnitFile.Error = append(aUnitFile.Error, aUnitError) - aUnitError = checkstyleError{} - - } else { - - log.Entry().Info("unit test:", aUnitError.Source, "- was successful") - - } - - } - - fileName, err = getFileName(config, client, testClass.URI, objectName) - if err != nil { - return parsedResult, errors.Wrap(err, "parse AUnit Result failed") - - } - } - - aUnitFile.Name, err = constructPath(config, client, fileName, objectName, objectType) - log.Entry().Error("file path: ", aUnitFile.Name) - if err != nil { - - return parsedResult, errors.Wrap(err, "parse AUnit Result failed") - } - parsedResult.File = append(parsedResult.File, aUnitFile) - aUnitFile = file{} - - } - - body, _ := xml.Marshal(parsedResult) - - writeErr := ioutil.WriteFile(config.AUnitResultsFileName, body, 0644) - - if writeErr != nil { - log.Entry().Error("file AUnitResults.xml could not be created") - return parsedResult, fmt.Errorf("handling unit test results failed: %w", writeErr) - } - - log.Entry().Info("parse ABAP Unit Result finished") - return parsedResult, nil - -} - -func executeATCCheck(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, objects []repoObject) (error error) { - - log.Entry().Info("execute ATC Check started") - - var innerXml string - var result worklist - - for _, object := range objects { - - switch object.Type { - - case "CLAS": - innerXml = innerXml + `` - case "INTF": - innerXml = innerXml + `` - case "DEVC": - innerXml = innerXml + `` - case "FUGR": - innerXml = innerXml + `` - case "TABL": - innerXml = innerXml + `` - case "DTEL": - innerXml = innerXml + `` - case "DOMA": - innerXml = innerXml + `` - case "MSAG": - innerXml = innerXml + `` - case "PROG": - innerXml = innerXml + `` - default: - log.Entry().Warning("object Type " + object.Type + " is not supported!") - - } - - } - - var xmlBody = []byte(` - - - - ` + innerXml + - ` - - - `) - - worklist, err := getWorklist(config, client) - if err != nil { - return errors.Wrap(err, "execution of ATC Checks failed") - } - - err = startATCRun(config, client, xmlBody, worklist) - - if err != nil { - return errors.Wrap(err, "execution of ATC Checks failed") - } - - resp, err := getATCRun(config, client, worklist) - - if err != nil { - return errors.Wrap(err, "execution of ATC Checks failed") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyXML(resp, &result) - if parsingErr != nil { - log.Entry().Warning(parsingErr) - return nil - } - - atcRes, err := parseATCCheckResult(config, client, &result) - - if err != nil { - log.Entry().Error(err) - return errors.Wrap(err, "execution of ATC Checks failed") - } - - log.Entry().Info("execute ATC Checks finished.", atcRes.Text) - - return nil - -} -func startATCRun(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, xml []byte, worklistID string) (err error) { - - log.Entry().Info("ATC Run started") - - discHeader, discError := discoverServer(config, client) - if discError != nil { - return errors.Wrap(discError, "start of ATC run failed") - } - - if discHeader.Get("X-Csrf-Token") == "" { - return errors.Errorf("could not retrieve x-csrf-token from server") - } - - header := make(http.Header) - header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) - header.Add("Accept", "application/xml") - - url := config.Host + - "/sap/bc/adt/atc/runs?worklistId=" + worklistID + "&sap-client=" + config.Client - - resp, httpErr := client.SendRequest("POST", url, bytes.NewBuffer(xml), header, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return errors.Wrap(httpErr, "start of ATC run failed") - } else if resp == nil { - return errors.New("start of ATC run failed: did not retrieve a HTTP response") - } - - log.Entry().Info("ATC Run finished") - - return nil - -} - -func getATCRun(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, worklistID string) (response *http.Response, err error) { - - log.Entry().Info("get ATC Run Results started") - - header := make(http.Header) - - url := config.Host + - "/sap/bc/adt/atc/worklists/" + worklistID + "?sap-client=" + config.Client - - header.Add("Accept", "application/atc.worklist.v1+xml") - - resp, httpErr := client.SendRequest("GET", url, nil, header, nil) - - if httpErr != nil { - return response, errors.Wrap(httpErr, "get ATC run failed") - } else if resp == nil { - return response, errors.New("get ATC run failed: did not retrieve a HTTP response") - } - log.Entry().Info("get ATC Run Results finished") - return resp, nil - -} - -func getWorklist(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (worklistID string, error error) { - - url := config.Host + - "/sap/bc/adt/atc/worklists?checkVariant=" + config.AtcVariant + "&sap-client=" + config.Client - discHeader, discError := discoverServer(config, client) - - if discError != nil { - return worklistID, errors.Wrap(discError, "get worklist failed") - } - - if discHeader.Get("X-Csrf-Token") == "" { - return worklistID, errors.Errorf("could not retrieve x-csrf-token from server") - } - - header := make(http.Header) - header.Add("x-csrf-token", discHeader.Get("X-Csrf-Token")) - header.Add("Accept", "*/*") - - resp, httpErr := client.SendRequest("POST", url, nil, header, nil) - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return worklistID, errors.Wrap(httpErr, "get worklist failed") - } else if resp == nil { - return worklistID, errors.New("get worklist failed: did not retrieve a HTTP response") - } - location := resp.Header["Location"][0] - locationSlice := strings.Split(location, "/") - worklistID = locationSlice[len(locationSlice)-1] - log.Entry().Info("worklist id for ATC check: ", worklistID) - - return worklistID, nil -} - -func parseATCCheckResult(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, response *worklist) (atcResults checkstyle, error error) { - - log.Entry().Info("parse ATC Check Result started") - - var atcFile file - var subObject string - var aTCUnitError checkstyleError - - atcResults.Version = "1.0" - - for _, object := range response.Objects.Object { - - objectType := object.Type - objectName := object.Name - - for _, atcworklist := range object.Findings.Finding { - - log.Entry().Info("there is atc finding for object type: ", objectType+" object name: "+objectName) - - path, err := url.PathUnescape(atcworklist.Location) - - if err != nil { - return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") - - } - - if len(atcworklist.Atcfinding) > 0 { - - priority, err := strconv.Atoi(atcworklist.Priority) - - if err != nil { - return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") - - } - - switch priority { - case 1: - atcFailure = true - aTCUnitError.Severity = "error" - log.Entry().Error("atc issue with priority: 1 ") - case 2: - atcFailure = true - aTCUnitError.Severity = "error" - log.Entry().Error("atc issue with priority: 2 ") - case 3: - aTCUnitError.Severity = "warning" - log.Entry().Warning("atc issue with priority: 3 ") - default: - aTCUnitError.Severity = "info" - log.Entry().Info("atc issue with low priority ") - } - - log.Entry().Error("severity: ", aTCUnitError.Severity) - - if aTCUnitError.Line == "" { - - aTCUnitError.Line, err = findLine(config, client, path, objectName, objectType) - log.Entry().Info("line: ", aTCUnitError.Line) - - if err != nil { - log.Entry().Info(path) - log.Entry().Warning(err) - - } - - } - - if subObject != "" { - aTCUnitError.Source = objectName + "/" + strings.ToUpper(subObject) - } else { - aTCUnitError.Source = objectName - } - - aTCUnitError.Message = html.UnescapeString(atcworklist.CheckTitle + " " + atcworklist.MessageTitle) - log.Entry().Info("message: ", aTCUnitError.Message) - atcFile.Error = append(atcFile.Error, aTCUnitError) - aTCUnitError = checkstyleError{} - } - - if atcFile.Error[0].Message != "" { - - fileName, err := getFileName(config, client, path, objectName) - - if err != nil { - return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") - } - - atcFile.Name, err = constructPath(config, client, fileName, objectName, objectType) - log.Entry().Info("file path: ", atcFile.Name) - if err != nil { - return atcResults, errors.Wrap(err, "conversion of ATC check results to CheckStyle has failed") - } - atcResults.File = append(atcResults.File, atcFile) - atcFile = file{} - - } - - } - } - - atcBody, _ := xml.Marshal(atcResults) - - writeErr := ioutil.WriteFile(config.AtcResultsFileName, atcBody, 0644) - - if writeErr != nil { - log.Entry().Error("ATCResults.xml could not be created") - return atcResults, fmt.Errorf("handling atc results failed: %w", writeErr) - } - log.Entry().Info("parsing ATC check results to CheckStyle has finished.") - return atcResults, writeErr -} - -func constructPath(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, fileName string, objectName string, objectType string) (filePath string, error error) { - - targetDir, err := getTargetDir(config, client) - if err != nil { - return filePath, errors.Wrap(err, "path could not be constructed") - - } - - filePath = config.Workspace + "/" + targetDir + "/objects/" + strings.ToUpper(objectType) + "/" + strings.ToUpper(objectName) + "/" + fileName - return filePath, nil - -} - -func findLine(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, path string, objectName string, objectType string) (line string, error error) { - - regexLine := regexp.MustCompile(`.start=\d*`) - regexMethod := regexp.MustCompile(`.name=[a-zA-Z0-9_-]*;`) - - readableSource, err := checkReadableSource(config, client) - - if err != nil { - - return line, errors.Wrap(err, "could not find line in source code") - - } - - fileName, err := getFileName(config, client, path, objectName) - - if err != nil { - - return line, err - - } - - filePath, err := constructPath(config, client, fileName, objectName, objectType) - if err != nil { - return line, errors.Wrap(err, objectType+"/"+objectName+"could not find line in source code") - - } - - var absLine int - if readableSource { - - // the error line that we get from UnitTest Run or ATC Check is not aligned for the readable source, we need to calculated it - rawfile, err := ioutil.ReadFile(filePath) - - if err != nil { - - return line, errors.Wrapf(err, "could not find object in the workspace of your CI/CD tool ") - } - - file := string(rawfile) - - splittedfile := strings.Split(file, "\n") - - // CLAS/OSO - is unique identifier for protection section in CLAS - if strings.Contains(path, "CLAS/OSO") { - - for l, line := range splittedfile { - - if strings.Contains(line, "protected section.") { - absLine = l - break - } - - } - - // CLAS/OM - is unique identifier for method section in CLAS - } else if strings.Contains(path, "CLAS/OM") { - - methodName := regexMethod.FindString(path) - - if methodName != "" { - methodName = methodName[len(`.name=`) : len(methodName)-1] - - } - - for line, linecontent := range splittedfile { - - if strings.Contains(linecontent, "method"+" "+methodName) { - absLine = line - break - } - - } - - // CLAS/OSI - is unique identifier for private section in CLAS - } else if strings.Contains(path, "CLAS/OSI") { - - for line, linecontent := range splittedfile { - - if strings.Contains(linecontent, "private section.") { - absLine = line - break - } - - } - - } - - errLine := regexLine.FindString(path) - - if errLine != "" { - - errLine, err := strconv.Atoi(errLine[len(`.start=`):]) - if err == nil { - line = strconv.Itoa(absLine + errLine) - - } - - } - - } else { - // classic format - errLine := regexLine.FindString(path) - if errLine != "" { - line = errLine[len(`.start=`):] - - } - - } - - return line, nil -} -func getFileName(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, path string, objName string) (fileName string, error error) { - - readableSource, err := checkReadableSource(config, client) - if err != nil { - return fileName, errors.Wrap(err, "get file name has failed") - - } - - path, err = url.PathUnescape(path) - - var fileExtension string - fileExtensionLength := 30 - len(objName) - for i := 0; i < fileExtensionLength; i++ { - fileExtension += "=" - } - - if err != nil { - return fileName, errors.Wrap(err, "get file name has failed") - - } - - // INTERFACES - regexInterface := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/interfaces\/\w*`) - intf := regexInterface.FindString(path) - if intf != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".intf.abap" - } else { - fileName = "REPS " + strings.ToUpper(objName) + fileExtension + "IU.abap" - } - - } - // CLASSES DEFINITIONS - regexClasDef := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/definitions\/`) - clasDef := regexClasDef.FindString(path) - if clasDef != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.definitions.abap" - } else { - fileName = "CINC " + objName + fileExtension + "CCDEF.abap" - } - - } - - // CLASSES IMPLEMENTATIONS - regexClasImpl := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/implementations\/`) - clasImpl := regexClasImpl.FindString(path) - if clasImpl != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.implementations.abap" - } else { - fileName = "CINC " + objName + fileExtension + "CCIMP.abap" - } - - } - - // CLASSES MACROS - regexClasMacro := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/includes\/macros\/`) - clasMacro := regexClasMacro.FindString(path) - if clasMacro != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.macros.abap" - } else { - fileName = "CINC " + objName + fileExtension + "CCMAC.abap" - } - - } - - // TEST CLASSES - regexTestClass := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*#?\/?\w*\/?testclass`) - testClass := regexTestClass.FindString(path) - if testClass != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.testclasses.abap" - } else { - fileName = "CINC " + objName + fileExtension + "CCAU.abap" - } - - } - - // CLASS PROTECTED - regexClasProtected := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSO`) - classProtected := regexClasProtected.FindString(path) - if classProtected != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.abap" - } else { - fileName = "CPRO " + objName + ".abap" - } - - } - - // CLASS PRIVATE - regexClasPrivate := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OSI`) - classPrivate := regexClasPrivate.FindString(path) - if classPrivate != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.abap" - } else { - fileName = "CPRI " + objName + ".abap" - } - - } - - // CLASS METHOD - regexClasMethod := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#type=CLAS\/OM`) - classMethod := regexClasMethod.FindString(path) - if classMethod != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.abap" - } else { - - regexmethodName := regexp.MustCompile(`name=\w*`) - methodName := regexmethodName.FindString(path) - - fileName = "METH " + methodName[len(`name=`):] + ".abap" - } - - } - - // CLASS PUBLIC - regexClasPublic := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/\w*\/source\/main#start`) - classPublic := regexClasPublic.FindString(path) - if classPublic != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.abap" - } else { - fileName = "CPUB " + objName + ".abap" - } - - } - - // FUNCTION INCLUDE - regexFuncIncl := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/includes/\w*`) - - funcIncl := regexFuncIncl.FindString(path) - if funcIncl != "" && fileName == "" { - - regexSubObj := regexp.MustCompile(`includes\/\w*`) - subObject := regexSubObj.FindString(path) - subObject = subObject[len(`includes/`):] - - if readableSource { - - fileName = strings.ToLower(objName) + ".fugr." + strings.ToLower(subObject) + ".reps.abap" - } else { - fileName = "REPS " + strings.ToUpper(subObject) + ".abap" - } - - } - - // FUNCTION GROUP - regexFuncGr := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/source\/main`) - - funcGr := regexFuncGr.FindString(path) - if funcGr != "" && fileName == "" { - - if readableSource { - - fileName = strings.ToLower(objName) + ".fugr.sapl" + strings.ToLower(objName) + ".reps.abap" - } else { - fileName = "REPS SAPL" + objName + ".abap" - } - - } - - // FUNCTION MODULE - regexFuncMod := regexp.MustCompile(`\/sap\/bc\/adt\/functions\/groups\/\w*\/fmodules/\w*`) - funcMod := regexFuncMod.FindString(path) - if funcMod != "" && fileName == "" { - - regexSubObj := regexp.MustCompile(`includes\/\w*`) - subObject := regexSubObj.FindString(path) - subObject = subObject[len(`includes/`):] - - if readableSource { - - fileName = strings.ToLower(subObject) + ".func.abap" - } else { - fileName = "FUNC " + subObject + ".abap" - } - - } - // CLAS - regexClas := regexp.MustCompile(`\/sap\/bc\/adt\/oo\/classes\/` + strings.ToLower(objName)) - clas := regexClas.FindString(path) - if clas != "" && fileName == "" { - if readableSource { - - fileName = strings.ToLower(objName) + ".clas.abap" - } else { - - fileName = "CPUB " + objName + ".abap" - } - - } - - // PROGRAM - regexProg := regexp.MustCompile(`\/sap\/bc\/adt\/programs\/programs\/` + strings.ToLower(objName)) - prog := regexProg.FindString(path) - if prog != "" && fileName == "" { - - fileName = "REPS " + objName + ".abap" - - } - - // TABLES - regexTab := regexp.MustCompile(`\/sap\/bc\/adt\/ddic\/tables\/` + strings.ToLower(objName)) - tab := regexTab.FindString(path) - if tab != "" && fileName == "" { - - fileName = "TABL " + objName + ".asx.json" - - } - - return fileName, nil - -} - -func getTargetDir(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (string, error) { - - var targetDir string - - repository, err := getRepo(config, client) - - if err != nil { - return targetDir, err - } - - for _, config := range repository.Result.Config { - if config.Key == "VCS_TARGET_DIR" { - targetDir = config.Value - } - } - - return targetDir, nil - -} - -func checkReadableSource(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (readableSource bool, error error) { - - repoLayout, err := getRepositoryLayout(config, client) - if err != nil { - return readableSource, errors.Wrap(err, "could not check readable source format") - } - - if repoLayout.Layout.ReadableSource == "true" || repoLayout.Layout.ReadableSource == "only" || repoLayout.Layout.ReadableSource == "all" { - - readableSource = true - - } else { - - readableSource = false - - } - - return readableSource, nil -} - -func getRepo(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (repositoryResponse, error) { - - var repositoryResp repositoryResponse - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "?sap-client=" + config.Client - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return repositoryResponse{}, errors.Wrap(httpErr, "could not get repository") - } else if resp == nil { - return repositoryResponse{}, errors.New("could not get repository: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repositoryResp) - if parsingErr != nil { - return repositoryResponse{}, errors.Errorf("%v", parsingErr) - } - - return repositoryResp, nil - -} - -func getRepositoryLayout(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (layoutResponse, error) { - - var repoLayoutResponse layoutResponse - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "/layout?sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return layoutResponse{}, errors.Wrap(httpErr, "could not get repository layout") - } else if resp == nil { - return layoutResponse{}, errors.New("could not get repository layout: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &repoLayoutResponse) - if parsingErr != nil { - return layoutResponse{}, errors.Errorf("%v", parsingErr) - } - - return repoLayoutResponse, nil -} - -func getCommitList(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender) (commitResponse, error) { - - var commitResp commitResponse - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "/getCommit?sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return commitResponse{}, errors.Wrap(httpErr, "get repository history failed") - } else if resp == nil { - return commitResponse{}, errors.New("get repository history failed: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &commitResp) - if parsingErr != nil { - return commitResponse{}, errors.Errorf("%v", parsingErr) - } - - return commitResp, nil -} - -func getObjectDifference(config *gctsExecuteABAPUnitTestsOptions, fromCommit string, toCommit string, client piperhttp.Sender) (objectsResponse, error) { - var objectResponse objectsResponse - - url := config.Host + - "/sap/bc/cts_abapvcs/repository/" + config.Repository + - "/compareCommits?fromCommit=" + fromCommit + "&toCommit=" + toCommit + "&sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return objectsResponse{}, errors.Wrap(httpErr, "get object difference failed") - } else if resp == nil { - return objectsResponse{}, errors.New("get object difference failed: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectResponse) - if parsingErr != nil { - return objectsResponse{}, errors.Errorf("%v", parsingErr) - } - log.Entry().Info("get object differences: ", objectResponse.Objects) - return objectResponse, nil -} - -func getObjectInfo(config *gctsExecuteABAPUnitTestsOptions, client piperhttp.Sender, objectName string, objectType string) (objectInfo, error) { - - var objectMetInfoResponse objectInfo - url := config.Host + - "/sap/bc/cts_abapvcs/objects/" + objectType + "/" + objectName + - "?sap-client=" + config.Client - - resp, httpErr := client.SendRequest("GET", url, nil, nil, nil) - - defer func() { - if resp != nil && resp.Body != nil { - resp.Body.Close() - } - }() - - if httpErr != nil { - return objectInfo{}, errors.Wrap(httpErr, "resolve package failed") - } else if resp == nil { - return objectInfo{}, errors.New("resolve package failed: did not retrieve a HTTP response") - } - - parsingErr := piperhttp.ParseHTTPResponseBodyJSON(resp, &objectMetInfoResponse) - if parsingErr != nil { - return objectInfo{}, errors.Errorf("%v", parsingErr) - } - return objectMetInfoResponse, nil - -} - -type worklist struct { - XMLName xml.Name `xml:"worklist"` - Text string `xml:",chardata"` - ID string `xml:"id,attr"` - Timestamp string `xml:"timestamp,attr"` - UsedObjectSet string `xml:"usedObjectSet,attr"` - ObjectSetIsComplete string `xml:"objectSetIsComplete,attr"` - Atcworklist string `xml:"atcworklist,attr"` - ObjectSets struct { - Text string `xml:",chardata"` - ObjectSet []struct { - Text string `xml:",chardata"` - Name string `xml:"name,attr"` - Title string `xml:"title,attr"` - Kind string `xml:"kind,attr"` - } `xml:"objectSet"` - } `xml:"objectSets"` - Objects struct { - Text string `xml:",chardata"` - Object []struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Type string `xml:"type,attr"` - Name string `xml:"name,attr"` - PackageName string `xml:"packageName,attr"` - Author string `xml:"author,attr"` - Atcobject string `xml:"atcobject,attr"` - Adtcore string `xml:"adtcore,attr"` - Findings struct { - Text string `xml:",chardata"` - Finding []struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Location string `xml:"location,attr"` - Processor string `xml:"processor,attr"` - LastChangedBy string `xml:"lastChangedBy,attr"` - Priority string `xml:"priority,attr"` - CheckId string `xml:"checkId,attr"` - CheckTitle string `xml:"checkTitle,attr"` - MessageId string `xml:"messageId,attr"` - MessageTitle string `xml:"messageTitle,attr"` - ExemptionApproval string `xml:"exemptionApproval,attr"` - ExemptionKind string `xml:"exemptionKind,attr"` - Checksum string `xml:"checksum,attr"` - QuickfixInfo string `xml:"quickfixInfo,attr"` - Atcfinding string `xml:"atcfinding,attr"` - Link struct { - Text string `xml:",chardata"` - Href string `xml:"href,attr"` - Rel string `xml:"rel,attr"` - Type string `xml:"type,attr"` - Atom string `xml:"atom,attr"` - } `xml:"link"` - Quickfixes struct { - Text string `xml:",chardata"` - Manual string `xml:"manual,attr"` - Automatic string `xml:"automatic,attr"` - Pseudo string `xml:"pseudo,attr"` - } `xml:"quickfixes"` - } `xml:"finding"` - } `xml:"findings"` - } `xml:"object"` - } `xml:"objects"` -} - -type runResult struct { - XMLName xml.Name `xml:"runResult"` - Text string `xml:",chardata"` - Aunit string `xml:"aunit,attr"` - Program []struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Type string `xml:"type,attr"` - Name string `xml:"name,attr"` - URIType string `xml:"uriType,attr"` - Adtcore string `xml:"adtcore,attr"` - Alerts struct { - Text string `xml:",chardata"` - Alert struct { - Text string `xml:",chardata"` - HasSyntaxErrors string `xml:"hasSyntaxErrors,attr"` - Kind string `xml:"kind,attr"` - Severity string `xml:"severity,attr"` - Title string `xml:"title"` - Details struct { - Text string `xml:",chardata"` - Detail struct { - Text string `xml:",chardata"` - AttrText string `xml:"text,attr"` - } `xml:"detail"` - } `xml:"details"` - Stack struct { - Text string `xml:",chardata"` - StackEntry struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Description string `xml:"description,attr"` - } `xml:"stackEntry"` - } `xml:"stack"` - } `xml:"alert"` - } `xml:"alerts"` - - TestClasses struct { - Text string `xml:",chardata"` - TestClass []struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Type string `xml:"type,attr"` - Name string `xml:"name,attr"` - URIType string `xml:"uriType,attr"` - NavigationURI string `xml:"navigationUri,attr"` - DurationCategory string `xml:"durationCategory,attr"` - RiskLevel string `xml:"riskLevel,attr"` - TestMethods struct { - Text string `xml:",chardata"` - TestMethod []struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Type string `xml:"type,attr"` - Name string `xml:"name,attr"` - ExecutionTime string `xml:"executionTime,attr"` - URIType string `xml:"uriType,attr"` - NavigationURI string `xml:"navigationUri,attr"` - Unit string `xml:"unit,attr"` - Alerts struct { - Text string `xml:",chardata"` - Alert []struct { - Text string `xml:",chardata"` - Kind string `xml:"kind,attr"` - Severity string `xml:"severity,attr"` - Title string `xml:"title"` - Details struct { - Text string `xml:",chardata"` - Detail []struct { - Text string `xml:",chardata"` - AttrText string `xml:"text,attr"` - Details struct { - Text string `xml:",chardata"` - Detail []struct { - Text string `xml:",chardata"` - AttrText string `xml:"text,attr"` - } `xml:"detail"` - } `xml:"details"` - } `xml:"detail"` - } `xml:"details"` - Stack struct { - Text string `xml:",chardata"` - StackEntry struct { - Text string `xml:",chardata"` - URI string `xml:"uri,attr"` - Type string `xml:"type,attr"` - Name string `xml:"name,attr"` - Description string `xml:"description,attr"` - } `xml:"stackEntry"` - } `xml:"stack"` - } `xml:"alert"` - } `xml:"alerts"` - } `xml:"testMethod"` - } `xml:"testMethods"` - } `xml:"testClass"` - } `xml:"testClasses"` - } `xml:"program"` -} - -type gctsException struct { - Message string `json:"message"` - Description string `json:"description"` - Code int `json:"code"` -} - -type gctsLogs struct { - Time int `json:"time"` - User string `json:"user"` - Section string `json:"section"` - Action string `json:"action"` - Severity string `json:"severity"` - Message string `json:"message"` - Code string `json:"code"` -} - -type commit struct { - ID string `json:"id"` -} - -type commitResponse struct { - Commits []commit `json:"commits"` - ErrorLog []gctsLogs `json:"errorLog"` - Log []gctsLogs `json:"log"` - Exception gctsException `json:"exception"` -} - -type objectInfo struct { - Pgmid string `json:"pgmid"` - Object string `json:"object"` - ObjName string `json:"objName"` - Srcsystem string `json:"srcsystem"` - Author string `json:"author"` - Devclass string `json:"devclass"` -} - -type repoConfig struct { - Key string `json:"key"` - Value string `json:"value"` - Cprivate string `json:"cprivate"` - Cprotected string `json:"cprotected"` - Cvisible string `json:"cvisible"` - Category string `json:"category"` - Scope string `json:"scope"` - ChangedAt float64 `json:"changeAt"` - ChangedBy string `json:"changedBy"` -} - -type repository struct { - Rid string `json:"rid"` - Name string `json:"name"` - Role string `json:"role"` - Type string `json:"type"` - Vsid string `json:"vsid"` - PrivateFlag string `json:"privateFlag"` - Status string `json:"status"` - Branch string `json:"branch"` - Url string `json:"url"` - CreatedBy string `json:"createdBy"` - CreatedDate string `json:"createdDate"` - Config []repoConfig `json:"config"` - Objects int `json:"objects"` - CurrentCommit string `json:"currentCommit"` -} - -type repositoryResponse struct { - Result repository `json:"result"` - Exception gctsException `json:"exception"` -} - -type objects struct { - Name string `json:"name"` - Type string `json:"type"` - Action string `json:"action"` -} -type objectsResponse struct { - Objects []objects `json:"objects"` - Log []gctsLogs `json:"log"` - Exception gctsException `json:"exception"` - ErrorLogs []gctsLogs `json:"errorLog"` -} - -type repoObject struct { - Pgmid string `json:"pgmid"` - Object string `json:"object"` - Type string `json:"type"` - Description string `json:"description"` -} - -type repoObjectResponse struct { - Objects []repoObject `json:"objects"` - Log []gctsLogs `json:"log"` - Exception gctsException `json:"exception"` - ErrorLogs []gctsLogs `json:"errorLog"` -} - -type layout struct { - FormatVersion int `json:"formatVersion"` - Format string `json:"format"` - ObjectStorage string `json:"objectStorage"` - MetaInformation string `json:"metaInformation"` - TableContent string `json:"tableContent"` - Subdirectory string `json:"subdirectory"` - ReadableSource string `json:"readableSource"` - KeepClient string `json:"keepClient"` -} - -type layoutResponse struct { - Layout layout `json:"layout"` - Log []gctsLogs `json:"log"` - Exception string `json:"exception"` - ErrorLogs []gctsLogs `json:"errorLog"` -} - -type checkstyleError struct { - Text string `xml:",chardata"` - Message string `xml:"message,attr"` - Source string `xml:"source,attr"` - Line string `xml:"line,attr"` - Severity string `xml:"severity,attr"` -} - -type file struct { - Text string `xml:",chardata"` - Name string `xml:"name,attr"` - Error []checkstyleError `xml:"error"` -} - -type checkstyle struct { - XMLName xml.Name `xml:"checkstyle"` - Text string `xml:",chardata"` - Version string `xml:"version,attr"` - File []file `xml:"file"` + gctsExecuteABAPQualityChecks(qualityChecksConfig, telemetryData) } diff --git a/cmd/gctsExecuteABAPUnitTests_generated.go b/cmd/gctsExecuteABAPUnitTests_generated.go index 3a76cb6a4..0f0689731 100644 --- a/cmd/gctsExecuteABAPUnitTests_generated.go +++ b/cmd/gctsExecuteABAPUnitTests_generated.go @@ -136,8 +136,8 @@ func addGctsExecuteABAPUnitTestsFlags(cmd *cobra.Command, stepConfig *gctsExecut cmd.Flags().BoolVar(&stepConfig.AUnitTest, "aUnitTest", true, "Indication whether you want to execute the unit test checks.") cmd.Flags().BoolVar(&stepConfig.AtcCheck, "atcCheck", true, "Indication whether you want to execute the ATC checks.") cmd.Flags().StringVar(&stepConfig.AtcVariant, "atcVariant", `DEFAULT_REMOTE_REF`, "Variant for ATC checks") - cmd.Flags().StringVar(&stepConfig.Scope, "scope", `repository`, "Scope of objects for which you want to execute the checks:\n\n * localChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the local repository. The checks are executed for the individual objects.\n * remoteChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the remote repository. The checks are executed for the individual objects.\n * localChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the local repository. All objects are resolved into packages. The checks are executed for the packages.\n * remoteChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the remote repository. All objects are resolved into packages. The checks are executed for the packages.\n * repository - all objects that are part of the local repository. The checks are executed for the individual objects. Packages (DEVC) are excluded. This is the default scope.\n * packages - all packages that are part of the local repository . The checks are executed for the packages.\n") - cmd.Flags().StringVar(&stepConfig.Commit, "commit", os.Getenv("PIPER_commit"), "ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes localChangedObjects, remoteChangedObjects, localChangedPackages and remoteChangedPackages secifying a commit it's mandatory.") + cmd.Flags().StringVar(&stepConfig.Scope, "scope", `repository`, "Scope of objects for which you want to execute the checks:\n\n * localChangedObjects - object delta derived from last activity in the local repository. The checks are executed for the individual objects.\n * remoteChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the remote repository. The checks are executed for the individual objects.\n * localChangedPackages - object delta derived from last activity in the local repository. All objects are resolved into packages. The checks are executed for the packages.\n * remoteChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the remote repository. All objects are resolved into packages. The checks are executed for the packages.\n * repository - all objects that are part of the local repository. The checks are executed for the individual objects. Packages (DEVC) are excluded. This is the default scope.\n * packages - all packages that are part of the local repository . The checks are executed for the packages.\n") + cmd.Flags().StringVar(&stepConfig.Commit, "commit", os.Getenv("PIPER_commit"), "ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes remoteChangedObjects and remoteChangedPackages secifying a commit it's mandatory.") cmd.Flags().StringVar(&stepConfig.Workspace, "workspace", os.Getenv("PIPER_workspace"), "Absolute path to directory which contains the source code that your CI/CD tool checks out. For example in Jenkins, the workspace parameter is /var/jenkins_home/workspace//") cmd.Flags().StringVar(&stepConfig.AtcResultsFileName, "atcResultsFileName", `ATCResults.xml`, "Specifies output file name for the results from the ATC checks") cmd.Flags().StringVar(&stepConfig.AUnitResultsFileName, "aUnitResultsFileName", `AUnitResults.xml`, "Specifies output file name for the results from the AUnit tests") diff --git a/cmd/gctsExecuteABAPUnitTests_test.go b/cmd/gctsExecuteABAPUnitTests_test.go index 32449d6f3..31d6c76fc 100644 --- a/cmd/gctsExecuteABAPUnitTests_test.go +++ b/cmd/gctsExecuteABAPUnitTests_test.go @@ -1,1801 +1,9 @@ package cmd import ( - "bytes" - "encoding/xml" - "io" - "io/ioutil" - "net/http" "testing" - - piperhttp "github.com/SAP/jenkins-library/pkg/http" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" ) -func TestDiscoverServerSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Scope: "localChangedObjects", - Commit: "0123456789abcdefghijkl", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("discover server successful", func(t *testing.T) { - - httpClient := httpMockGcts{ - StatusCode: 200, - Header: map[string][]string{"x-csrf-token": {"ZegUEgfa50R7ZfGGxOtx2A=="}}, - ResponseBody: ` - - - `} - - header, err := discoverServer(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/adt/core/discovery?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check header", func(t *testing.T) { - assert.Equal(t, http.Header{"x-csrf-token": []string{"ZegUEgfa50R7ZfGGxOtx2A=="}}, *header) - }) - - } - - }) -} - -func TestDiscoverServerFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost3.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Scope: "localChangedObjects", - Commit: "0123456789abcdefghijkl", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("a http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 403, ResponseBody: ` - - - - Service cannot be reached - - - - -
-
-

403 Forbidden

-
-
- - - - `} - - header, err := discoverServer(&config, &httpClient) - - t.Run("check error", func(t *testing.T) { - assert.EqualError(t, err, "discovery of the ABAP server failed: a http error occurred") - }) - - t.Run("check header", func(t *testing.T) { - assert.Equal(t, (*http.Header)(nil), header) - }) - - }) - - t.Run("discovery response is nil", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: ``} - - header, err := discoverServer(&config, &httpClient) - - t.Run("check error", func(t *testing.T) { - assert.EqualError(t, err, "discovery of the ABAP server failed: did not retrieve a HTTP response") - }) - - t.Run("check header", func(t *testing.T) { - assert.Equal(t, (*http.Header)(nil), header) - }) - - }) - - t.Run("discovery header is nil", func(t *testing.T) { - - httpClient := httpMockGctsT{ - StatusCode: 200, - Header: nil, - ResponseBody: ` - - - `} - - header, err := discoverServer(&config, &httpClient) - - t.Run("check error", func(t *testing.T) { - assert.EqualError(t, err, "discovery of the ABAP server failed: did not retrieve a HTTP response") - }) - - t.Run("check header", func(t *testing.T) { - assert.Equal(t, (*http.Header)(nil), header) - }) - - }) -} - -func TestGetLocalObjectsSuccess(t *testing.T) { - - t.Run("return multiple objects successfully", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - - Object: "ZCL_GCTS", - Type: "CLAS", - } - object2 := repoObject{ - - Object: "ZP_GCTS", - Type: "DEVC", - } - object3 := repoObject{ - - Object: "ZIF_GCTS_API", - Type: "INTF", - } - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - repoObjects = append(repoObjects, object2) - repoObjects = append(repoObjects, object3) - - objects, err := getLocalObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - var repoObjects []repoObject - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} - - objects, err := getLocalObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetLocalObjectsFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGcts{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getLocalObjects(&config, &httpClient) - - assert.EqualError(t, err, "get local changed objects failed: could not get repository: a http error occurred") - }) -} - -func TestGetRemoteObjectsSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("return multiple objects successfully", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - - Object: "ZCL_GCTS", - Type: "CLAS", - } - object2 := repoObject{ - - Object: "ZP_GCTS", - Type: "DEVC", - } - object3 := repoObject{ - - Object: "ZIF_GCTS_API", - Type: "INTF", - } - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - repoObjects = append(repoObjects, object2) - repoObjects = append(repoObjects, object3) - - objects, err := getRemoteObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} - var repoObjects []repoObject - objects, err := getRemoteObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetRemoteObjectsFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGcts{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getRemoteObjects(&config, &httpClient) - - assert.EqualError(t, err, "get remote changed objects failed: get repository history failed: a http error occurred") - }) -} - -func TestGetLocalPackagesSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedPackages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("return multiple objects successfully", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - - Object: "SGCTS", - - Type: "DEVC", - } - object2 := repoObject{ - - Object: "SGCTS_2", - Type: "DEVC", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - repoObjects = append(repoObjects, object2) - - objects, err := getLocalPackages(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedObjects", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} - var repoObjects []repoObject - objects, err := getLocalPackages(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetLocalPackagesFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "localChangedPackages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getLocalPackages(&config, &httpClient) - - assert.EqualError(t, err, "get local changed packages failed: could not get repository: a http error occurred") - }) -} - -func TestGetRemotePackagesSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedPackages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("return multiple objects successfully", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - - Object: "SGCTS", - - Type: "DEVC", - } - object2 := repoObject{ - - Object: "SGCTS_2", - Type: "DEVC", - } - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - repoObjects = append(repoObjects, object2) - - objects, err := getRemotePackages(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedPackages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} - var repoObjects []repoObject - objects, err := getRemoteObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetRemotePackagesFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "remoteChangedPackages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getRemotePackages(&config, &httpClient) - - assert.EqualError(t, err, "get remote changed packages failed: get repository history failed: a http error occurred") - }) -} - -func TestGetPackagesSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "packages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("return multiple objects successfully", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - - Pgmid: "R3TR", - Object: "ZP_GCTS", - Type: "DEVC", - Description: "Package(ABAP Objects)", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - - objects, err := getPackages(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "packages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - httpClient := httpMockGcts{StatusCode: 200, ResponseBody: `{}`} - var repoObjects []repoObject - objects, err := getPackages(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/objects?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetPackagesFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "packages", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getPackages(&config, &httpClient) - - assert.EqualError(t, err, "get packages failed: could not get repository objects: a http error occurred") - }) -} - -func TestGetRepositoryObjectsSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("return multiple objects successfully", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object1 := repoObject{ - Pgmid: "R3TR", - Object: "ZCL_GCTS", - Type: "CLAS", - Description: "Class (ABAP Objects)", - } - - object3 := repoObject{ - Pgmid: "R3TR", - Object: "ZIF_GCTS_API", - Type: "INTF", - Description: "Interface (ABAP Objects)", - } - var repoObjects []repoObject - repoObjects = append(repoObjects, object1) - repoObjects = append(repoObjects, object3) - - objects, err := getRepositoryObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - - } - - }) - - t.Run("no objects returned by http call", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo2", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: `{}`} - var repoObjects []repoObject - objects, err := getRepositoryObjects(&config, &httpClient) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/objects?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check package objects", func(t *testing.T) { - assert.Equal(t, repoObjects, objects) - }) - } - - }) -} - -func TestGetRepositoryObjectsFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - } - - t.Run("http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 500, ResponseBody: ` - { - "exception": "No relation between system and repository" - } - `} - - _, err := getRepositoryObjects(&config, &httpClient) - - assert.EqualError(t, err, "could not get repository objects: a http error occurred") - }) -} - -func TestExecuteAUnitTestSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - AUnitResultsFileName: "AUnitResults.xml", - } - - t.Run("all unit tests were successful", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object := repoObject{ - Pgmid: "R3TR", - Object: "ZCL_GCTS", - Type: "CLAS", - Description: "Clas Object", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object) - - err := executeAUnitTest(&config, &httpClient, repoObjects) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - } - }) - - t.Run("no unit tests found", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200, ResponseBody: ` - - - - - The task definition does not refer to any test - - - - `} - object := repoObject{ - Pgmid: "R3TR", - Object: "ZP_NON_EXISTANT", - Type: "CLAS", - Description: "Clas Object", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object) - err := executeAUnitTest(&config, &httpClient, repoObjects) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - } - }) -} - -func TestExecuteAUnitTestFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - AUnitResultsFileName: "AUnitResults.xml", - } - - var repoObjects []repoObject - - t.Run("a http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 403, ResponseBody: ` - CSRF token validation failed - `} - - header := make(http.Header) - header.Add("Accept", "application/atomsvc+xml") - header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") - header.Add("saml2", "disabled") - - err := executeAUnitTest(&config, &httpClient, repoObjects) - - assert.EqualError(t, err, "execute of Aunit test has failed: run of unit tests failed: discovery of the ABAP server failed: a http error occurred") - - }) -} - -func TestExecuteATCCheckSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "DEFAULT_REMOTE_REF", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - header := make(http.Header) - header.Add("Accept", "application/atomsvc+xml") - header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") - header.Add("saml2", "disabled") - - t.Run("atc checks were found", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - object := repoObject{ - Pgmid: "R3TR", - Object: "ZCL_GCTS", - Type: "CLAS", - Description: "Clas Object", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object) - - err := executeATCCheck(&config, &httpClient, repoObjects) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - } - }) - - t.Run("no ATC Checks were found", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "CUSTOM_REMOTE_REF", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - object := repoObject{ - Pgmid: "R3TR", - Object: "ZP_NON_EXISTANT", - Type: "CLAS", - Description: "Clas Object", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object) - err := executeATCCheck(&config, &httpClient, repoObjects) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/adt/atc/worklists/3E3D0764F95Z01ABCDHEF9C9F6B5C14P?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - } - }) -} - -func TestExecuteATCCheckFailure(t *testing.T) { - - object := repoObject{ - Pgmid: "R3TR", - Object: "ZP_PIPER", - Type: "CLAS", - Description: "Clas Object", - } - - var repoObjects []repoObject - repoObjects = append(repoObjects, object) - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - t.Run("a http error occurred", func(t *testing.T) { - - httpClient := httpMockGcts{StatusCode: 403, ResponseBody: ` - CSRF token validation failed - `} - - header := make(http.Header) - header.Add("Accept", "application/atomsvc+xml") - header.Add("x-csrf-token", "ZegUEgfa50R7ZfGGxOtx2A==") - header.Add("saml2", "disabled") - - err := executeATCCheck(&config, &httpClient, repoObjects) - - assert.EqualError(t, err, "execution of ATC Checks failed: get worklist failed: discovery of the ABAP server failed: a http error occurred") - - }) -} - -func TestParseAUnitResultSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AUnitResultsFileName: "AUnitResults.xml", - } - - t.Run("unit test is successful", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - var xmlBody = []byte(` - - - - - - - - - - - - - `) - - var resp *runResult - xml.Unmarshal(xmlBody, &resp) - - parsedRes, err := parseUnitResult(&config, &httpClient, resp) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check file name", func(t *testing.T) { - assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS/CINC ZCL_GCTS======================CCAU.abap", parsedRes.File[0].Name) - }) - - } - }) - - t.Run("unit test failed", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - var xmlBody = []byte(` - - - - - - - - - Critical Assertion Error: 'Check: ASSERT_EQUALS' -
- -
- -
-
- -
- - - -
-
-
-
-
-
-
-
`) - - var resp *runResult - xml.Unmarshal(xmlBody, &resp) - - parsedRes, err := parseUnitResult(&config, &httpClient, resp) - - if assert.NoError(t, err) { - - t.Run("check url", func(t *testing.T) { - assert.Equal(t, "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000", httpClient.URL) - }) - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "GET", httpClient.Method) - }) - - t.Run("check file name", func(t *testing.T) { - assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS_PIPER_DEMO/CINC ZCL_GCTS_PIPER_DEMO===========CCAU.abap", parsedRes.File[0].Name) - }) - - t.Run("check line number", func(t *testing.T) { - assert.Equal(t, "21", parsedRes.File[0].Error[0].Line) - }) - - t.Run("check severity", func(t *testing.T) { - assert.Equal(t, "error", parsedRes.File[0].Error[0].Severity) - }) - - t.Run("check source", func(t *testing.T) { - assert.Equal(t, "LTCL_MASTER/CHECK", parsedRes.File[0].Error[0].Source) - }) - - t.Run("check message", func(t *testing.T) { - assert.Equal(t, " Different Values: Expected [Hello] Actual [Hello2] Test 'LTCL_MASTER->CHECK' in Main Program 'ZCL_GCTS_PIPER_DEMO===========CP'.", parsedRes.File[0].Error[0].Message) - }) - - } - }) +func TestRunGctsExecuteABAPUnitTests(t *testing.T) { } - -func TestParseAUnitResultFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "DEFAULT_REMOTE_REF", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AUnitResultsFileName: "AUnitResults.xml", - } - - t.Run("parser fails", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 403} - - var xmlBody = []byte(` - - - - - - - - - Critical Assertion Error: 'Check: ASSERT_EQUALS' -
- -
- -
-
- -
- - - -
-
-
-
-
-
-
-
`) - - var resp *runResult - xml.Unmarshal(xmlBody, &resp) - - parsedRes, err := parseUnitResult(&config, &httpClient, resp) - - if assert.Error(t, err) { - - t.Run("check method", func(t *testing.T) { - assert.Equal(t, "parse AUnit Result failed: get file name has failed: could not check readable source format: could not get repository layout: a http error occurred", err.Error()) - }) - - assert.NotEmpty(t, parsedRes, "results are not empty") - - } - }) - -} - -func TestParseATCCheckResultSuccess(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "DEFAULT_REMOTE_REF", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - t.Run("atc found", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 200} - - var xmlBody = []byte(` - - - - - - - - - - - - - - - - `) - - var resp *worklist - - xml.Unmarshal(xmlBody, &resp) - - parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) - - if assert.NoError(t, err) { - - t.Run("check file name", func(t *testing.T) { - assert.Equal(t, "/var/jenkins_home/workspace/myFirstPipeline//objects/CLAS/ZCL_GCTS/CPRI ZCL_GCTS.abap", parsedRes.File[0].Name) - }) - - t.Run("check line number", func(t *testing.T) { - assert.Equal(t, "20", parsedRes.File[0].Error[0].Line) - }) - - t.Run("check severity", func(t *testing.T) { - assert.Equal(t, "error", parsedRes.File[0].Error[0].Severity) - }) - - t.Run("check source", func(t *testing.T) { - assert.Equal(t, "ZCL_GCTS", parsedRes.File[0].Error[0].Source) - }) - - t.Run("check message", func(t *testing.T) { - assert.Equal(t, "Package Check (Remote-enabled) Package Violation - Error", parsedRes.File[0].Error[0].Message) - }) - - assert.NotEmpty(t, parsedRes, "results are not empty") - - } - }) - - t.Run("no ATC Checks were found", func(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "DEFAULT_REMOTE_REF", - Scope: "repository", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - httpClient := httpMockGctsT{StatusCode: 200} - - var xmlBody = []byte(` - - - - - - - `) - var resp *worklist - xml.Unmarshal(xmlBody, &resp) - parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) - - if assert.NoError(t, err) { - - assert.Equal(t, parsedRes.Version, "1.0") - - } - }) - -} - -func TestParseATCCheckResultFailure(t *testing.T) { - - config := gctsExecuteABAPUnitTestsOptions{ - Host: "http://testHost.com:50000", - Client: "000", - Repository: "testRepo", - Username: "testUser", - Password: "testPassword", - Commit: "0123456789abcdefghijkl", - AtcVariant: "DEFAULT_REMOTE_REF", - Scope: "repsoitory", - Workspace: "/var/jenkins_home/workspace/myFirstPipeline", - AtcResultsFileName: "ATCResults.xml", - } - - t.Run("a http error occurred", func(t *testing.T) { - - httpClient := httpMockGctsT{StatusCode: 403} - - var xmlBody = []byte(` - - - - - - - - - - - - - - - - `) - - var resp *worklist - xml.Unmarshal(xmlBody, &resp) - parsedRes, err := parseATCCheckResult(&config, &httpClient, resp) - - assert.EqualError(t, err, "conversion of ATC check results to CheckStyle has failed: get file name has failed: could not check readable source format: could not get repository layout: a http error occurred") - assert.NotEmpty(t, parsedRes) - }) - -} - -type httpMockGctsT struct { - Method string // is set during test execution - URL string // is set before test execution - Header map[string][]string // is set before test execution - ResponseBody string // is set before test execution - Options piperhttp.ClientOptions // is set during test - StatusCode int // is set during test -} - -func (c *httpMockGctsT) SetOptions(options piperhttp.ClientOptions) { - c.Options = options -} - -func (c *httpMockGctsT) SendRequest(method string, url string, r io.Reader, header http.Header, cookies []*http.Cookie) (*http.Response, error) { - - c.Method = method - c.URL = url - - switch url { - - case "http://testHost.com:50000/sap/bc/adt/core/discovery?sap-client=000": - - c.Header = map[string][]string{"X-Csrf-Token": {"ZegUEgfa50R7ZfGGxOtx2A=="}} - - c.ResponseBody = ` - - - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo?sap-client=000": - - c.ResponseBody = ` - { - "result": { - - "rid": "testRepo", - "name": "testRepo", - "role": "PROVIDED", - "type": "GIT", - "vsid": "vSID", - "privateFlag": "false", - "url": "http://github.com/testRepo", - "createdBy": "testUser", - "createdDate": "02/02/2022", - "objects": 3, - "currentCommit": "xyz987654321" - } - } - ` - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2?sap-client=000": - - c.ResponseBody = ` - { - "result": { - - "rid": "testRepo2", - "name": "testRepo2", - "role": "PROVIDED", - "type": "GIT", - "vsid": "vSID", - "privateFlag": "false", - "url": "http://github.com/testRepo2", - "createdBy": "testUser", - "createdDate": "02/02/2022", - "objects": 3, - "currentCommit": "xyz987654321" - } - - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/getCommit?sap-client=000": - - c.ResponseBody = ` - { - "commits": [ - { - - "id": "0123456789abcdefghijkl" - - }, - { - - "id": "7845abaujztrw785" - }, - { - - "id": "45poiztr785423" - } - ] - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo2/getCommit?sap-client=000": - - c.ResponseBody = ` - { - "commits": [ - { - - "id": "0123456789abcdefghijkl" - - }, - { - - "id": "7845abaujztrw785" - }, - { - - "id": "45poiztr785423" - } - ] - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=xyz987654321&toCommit=0123456789abcdefghijkl&sap-client=000": - - c.ResponseBody = ` - { - "objects": [ - { - - "name": "ZCL_GCTS", - "type": "CLAS", - "action": "Class (ABAP Objects)" - }, - { - - "name": "ZP_GCTS", - "type": "DEVC", - "action": "Package(ABAP Objects)" - }, - { - - "name": "ZIF_GCTS_API", - "type": "INTF", - "action": "Interface (ABAP Objects)" - } - ] - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/compareCommits?fromCommit=7845abaujztrw785&toCommit=0123456789abcdefghijkl&sap-client=000": - - c.ResponseBody = ` - { - "objects": [ - { - - "name": "ZCL_GCTS", - "type": "CLAS", - "action": "Class (ABAP Objects)" - }, - { - - "name": "ZP_GCTS", - "type": "DEVC", - "action": "Package(ABAP Objects)" - }, - { - - "name": "ZIF_GCTS_API", - "type": "INTF", - "action": "Interface (ABAP Objects)" - } - ] - } - ` - case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/CLAS/ZCL_GCTS?sap-client=000": - - c.ResponseBody = ` - - { - - "pgmid": "R3TR", - "object": "CLAS", - "objName": "ZCL_GCTS", - "srcsystem": "src", - "author": "HUGO", - "devclass": "SGCTS" - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/INTF/ZIF_GCTS_API?sap-client=000": - - c.ResponseBody = ` - - { - - "pgmid": "R3TR", - "object": "INTF", - "objName": "ZIF_GCTS_API", - "srcsystem": "src", - "author": "HUGO", - "devclass": "SGCTS_2" - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/objects/DEVC/ZP_GCTS?sap-client=000": - - c.ResponseBody = ` - - { - - "pgmid": "R3TR", - "object": "DEVC", - "objName": "ZP_GCTS", - "srcsystem": "src", - "author": "HUGO", - "devclass": "SGCTS" - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/layout?sap-client=000": - - c.ResponseBody = - `{ - "layout": { - "formatVersion": 5, - "format": "json", - "objectStorage": "plain", - "metaInformation": ".gctsmetadata/", - "tableContent": "true", - "subdirectory": "src/", - "readableSource": "false" - } - } - ` - - case "http://testHost.com:50000/sap/bc/cts_abapvcs/repository/testRepo/objects?sap-client=000": - - c.ResponseBody = ` - { - "objects": [ - { - "pgmid": "R3TR", - "object": "ZCL_GCTS", - "type": "CLAS", - "description": "Class (ABAP Objects)" - }, - { - - "pgmid": "R3TR", - "object": "ZP_GCTS", - "type": "DEVC", - "description": "Package(ABAP Objects)" - }, - { - "pgmid": "R3TR", - "object": "ZIF_GCTS_API", - "type": "INTF", - "description": "Interface (ABAP Objects)" - } - ] - } - ` - - case "http://testHost.com:50000/sap/bc/adt/abapunit/testruns?sap-client=000": - - c.Header = map[string][]string{"Accept": {"application/xml"}} - c.Header = map[string][]string{"x-csrf-token": {"ZegUEgfa50R7ZfGGxOtx2A=="}} - c.Header = map[string][]string{"Content-Type": {"application/vnd.sap.adt.abapunit.testruns.result.v1+xml"}} - - c.ResponseBody = ` - - - - - - - - - - - - - ` - - case "http://testHost.com:50000/sap/bc/adt/atc/worklists?checkVariant=DEFAULT_REMOTE_REF&sap-client=000": - - c.Header = map[string][]string{"Location": {"/atc/worklists/worklistId/123Z076495C01ABCDEF9C9F6B257OD70"}} - - case "http://testHost.com:50000/sap/bc/adt/atc/worklists?checkVariant=CUSTOM_REMOTE_REF&sap-client=000": - - c.Header = map[string][]string{"Location": {"/atc/worklists/worklistId/3E3D0764F95Z01ABCDHEF9C9F6B5C14P"}} - - case "http://testHost.com:50000/sap/bc/adt/atc/runs?worklistId=123Z076495C01ABCDEF9C9F6B257OD70?sap-client=000": - - c.ResponseBody = - ` - - 123Z076495C01ABCDEF9C9F6B257OD70 - 2021-11-29T14:46:46Z - - - FINDING_STATS - 0,0,1 - - - ` - - case "http://testHost.com:50000/sap/bc/adt/atc/worklists/3E3D0764F95Z01ABCDHEF9C9F6B5C14P?sap-client=000": - c.ResponseBody = - ` - - - - - - - ` - - case "http://testHost.com:50000/sap/bc/adt/atc/worklists/123Z076495C01ABCDEF9C9F6B257OD70?sap-client=000": - c.ResponseBody = - ` - - - - - - - - - - - - - - - -` - } - - if r != nil { - _, err := ioutil.ReadAll(r) - - if err != nil { - return nil, err - } - } - - res := http.Response{ - StatusCode: c.StatusCode, - Header: c.Header, - Body: ioutil.NopCloser(bytes.NewReader([]byte(c.ResponseBody))), - } - - if c.StatusCode >= 400 { - return &res, errors.New("a http error occurred") - } - - return &res, nil -} diff --git a/cmd/metadata_generated.go b/cmd/metadata_generated.go index eec90298c..cb63188fb 100644 --- a/cmd/metadata_generated.go +++ b/cmd/metadata_generated.go @@ -45,6 +45,7 @@ func GetAllStepMetadata() map[string]config.StepData { "gctsCloneRepository": gctsCloneRepositoryMetadata(), "gctsCreateRepository": gctsCreateRepositoryMetadata(), "gctsDeploy": gctsDeployMetadata(), + "gctsExecuteABAPQualityChecks": gctsExecuteABAPQualityChecksMetadata(), "gctsExecuteABAPUnitTests": gctsExecuteABAPUnitTestsMetadata(), "gctsRollback": gctsRollbackMetadata(), "githubCheckBranchProtection": githubCheckBranchProtectionMetadata(), diff --git a/cmd/piper.go b/cmd/piper.go index 93139498f..6250f02a6 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -124,6 +124,7 @@ func Execute() { rootCmd.AddCommand(NpmExecuteScriptsCommand()) rootCmd.AddCommand(NpmExecuteLintCommand()) rootCmd.AddCommand(GctsCreateRepositoryCommand()) + rootCmd.AddCommand(GctsExecuteABAPQualityChecksCommand()) rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand()) rootCmd.AddCommand(GctsDeployCommand()) rootCmd.AddCommand(MalwareExecuteScanCommand()) diff --git a/documentation/docs/steps/gctsExecuteABAPQualityChecks.md b/documentation/docs/steps/gctsExecuteABAPQualityChecks.md new file mode 100644 index 000000000..bb23d6c33 --- /dev/null +++ b/documentation/docs/steps/gctsExecuteABAPQualityChecks.md @@ -0,0 +1,123 @@ +# ${docGenStepName} + +## ${docGenDescription} + +## Prerequisites + +* [ATC](https://help.sap.com/viewer/c238d694b825421f940829321ffa326a/202110.000/en-US/4ec5711c6e391014adc9fffe4e204223.html) checks are enabled in transaction ATC in the ABAP systems where you want to use the step. +* [gCTS](https://help.sap.com/viewer/4a368c163b08418890a406d413933ba7/latest/en-US/26c9c6c5a89244cb9506c253d36c3fda.html) is available and configured in the ABAP systems where you want to use the step. +* The [Warnings-Next-Generation](https://plugins.jenkins.io/warnings-ng/) Plugin is installed in Jenkins. + +## ${docGenParameters} + +## ${docGenConfiguration} + +## ${docJenkinsPluginDependencies} + +## Example + +Example configuration for the use in a Jenkinsfile. + +```groovy +gctsExecuteABAPQualityChecks( + script: this, + host: 'https://abap.server.com:port', + client: '000', + abapCredentialsId: 'ABAPUserPasswordCredentialsId', + repository: 'myrepo', + scope: 'remoteChangedObjects', + commit: "${GIT_COMMIT}", + workspace: "${WORKSPACE}" + + ) +``` + +Example configuration for the use in a yaml config file (such as `.pipeline/config.yaml`). + +```yaml +steps: + <...> + gctsExecuteABAPQualityChecks: + host: 'https://abap.server.com:port' + client: '000' + abapCredentialsId: 'ABAPUserPasswordCredentialsId' + repository: 'myrepo' + scope: 'remoteChangedObjects' + commit: '38abb4814ae46b98e8e6c3e718cf1782afa9ca90' + workspace: '/var/jenkins_home/workspace/myFirstPipeline' +``` + +Example configuration when you define scope: *repository* or *packages*. For these two cases you do not need to specify a *commit*. + +```yaml +steps: + <...> + gctsExecuteABAPQualityChecks: + host: 'https://abap.server.com:port' + client: '000' + abapCredentialsId: 'ABAPUserPasswordCredentialsId' + repository: 'myrepo' + scope: 'repository' + workspace: '/var/jenkins_home/workspace/myFirstPipeline' +``` + +Example configuration when you want to execute only ABAP Unit Test. + +```yaml +steps: + <...> + gctsExecuteABAPQualityChecks: + host: 'https://abap.server.com:port' + client: '000' + abapCredentialsId: 'ABAPUserPasswordCredentialsId' + repository: 'myrepo' + atcCheck: false + scope: 'packages' + workspace: '/var/jenkins_home/workspace/myFirstPipeline' +``` + +Example configuration for the use of *recordIssue* step to make the findings visible in Jenkins interface. + +```groovy +stage('ABAP Unit Tests') { + steps{ + + script{ + + try{ + gctsExecuteABAPQualityChecks( + script: this, + commit: "${GIT_COMMIT}", + workspace: "${WORKSPACE}") + } + catch (Exception ex) { + currentBuild.result = 'FAILURE' + unstable(message: "${STAGE_NAME} is unstable") + } + + } + } + } +stage('Results in Checkstyle') { + steps{ + + recordIssues( + enabledForFailure: true, aggregatingResults: true, + tools: [checkStyle(pattern: 'ATCResults.xml', reportEncoding: 'UTF8'),checkStyle(pattern: 'AUnitResults.xml', reportEncoding: 'UTF8')] + ) + + } + } + +} +``` + +**Note:** If you have disabled *atcCheck* or *aUnitTest*, than you also need to remove the corresponding *ATCResults.xml* or *AUnitResults.xml* from *recordIssues* step. In the example below the *atcCheck* was disabled, so *ATCResults.xml* was removed. + +```groovy +recordIssues( + enabledForFailure: true, aggregatingResults: true, + tools: [checkStyle(pattern: 'AUnitResults.xml', reportEncoding: 'UTF8')] + +) +``` diff --git a/documentation/docs/steps/gctsExecuteABAPUnitTests.md b/documentation/docs/steps/gctsExecuteABAPUnitTests.md index 2a33192a0..7a22b6f85 100644 --- a/documentation/docs/steps/gctsExecuteABAPUnitTests.md +++ b/documentation/docs/steps/gctsExecuteABAPUnitTests.md @@ -25,7 +25,7 @@ gctsExecuteABAPUnitTests( client: '000', abapCredentialsId: 'ABAPUserPasswordCredentialsId', repository: 'myrepo', - scope: 'localChangedObjects', + scope: 'remoteChangedObjects', commit: "${GIT_COMMIT}", workspace: "${WORKSPACE}" diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 1ec0667c1..6239fe067 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -95,7 +95,7 @@ nav: - gctsCloneRepository: steps/gctsCloneRepository.md - gctsCreateRepository: steps/gctsCreateRepository.md - gctsDeploy: steps/gctsDeploy.md - - gctsExecuteABAPUnitTests: steps/gctsExecuteABAPUnitTests.md + - gctsExecuteABAPQualityChecks: steps/gctsExecuteABAPQualityChecks.md - gctsRollback: steps/gctsRollback.md - githubCheckBranchProtection: steps/githubCheckBranchProtection.md - githubCommentIssue: steps/githubCommentIssue.md @@ -171,6 +171,7 @@ nav: - checkChangeInDevelopment: steps/checkChangeInDevelopment.md - npmExecute: steps/npmExecute.md - transportRequestUploadFile: steps/transportRequestUploadFile.md + - gctsExecuteABAPUnitTests: steps/gctsExecuteABAPUnitTests.md - 'Command line tool': cli/index.md theme: diff --git a/resources/metadata/gctsExecuteABAPQualityChecks.yaml b/resources/metadata/gctsExecuteABAPQualityChecks.yaml new file mode 100644 index 000000000..192ad07a7 --- /dev/null +++ b/resources/metadata/gctsExecuteABAPQualityChecks.yaml @@ -0,0 +1,150 @@ +metadata: + name: gctsExecuteABAPQualityChecks + description: Runs ABAP unit tests and ATC (ABAP Test Cockpit) checks for a specified object scope. + longDescription: | + This step executes ABAP unit test and ATC checks for a specified scope of objects that exist in a local Git repository on an ABAP system. + Depending on your use case, you can specify a scope of objects for which you want to execute the checks. In addition, you can choose whether you want to execute only ABAP units tests, or only ATC checks, or both. + By default, both checks are executed. + The results of the checks are stored in a [Checkstyle](https://checkstyle.sourceforge.io/) format. With the help of the Jenkins [Warnings-Next-Generation](https://plugins.jenkins.io/warnings-ng/) Plugin), you can view the issues found, and navigate to the exact line of the source code where the issue occurred. + To make the findings visible in Jenkins interface, you will need to use step recordIssues. An example will be shown in the Example section. +
+ You can use this step as of SAP S/4HANA 2020. + +spec: + inputs: + secrets: + - name: abapCredentialsId + description: ID taken from the Jenkins credentials store containing user name and password of the user that authenticates to the ABAP system on which you want to execute the checks. + type: jenkins + params: + - name: username + type: string + description: User that authenticates to the ABAP system. Note – Don´t provide this parameter directly. Either set it in the environment, or in the Jenkins credentials store, and provide the ID as value of the abapCredentialsId parameter. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + secret: true + resourceRef: + - name: abapCredentialsId + type: secret + param: username + - name: password + type: string + description: Password of the ABAP user that authenticates to the ABAP system. Note – Don´t provide this parameter directly. Either set it in the environment, or in the Jenkins credentials store, and provide the ID as value of the abapCredentialsId parameter. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + secret: true + resourceRef: + - name: abapCredentialsId + type: secret + param: password + - name: host + type: string + description: Protocol and host of the ABAP system, including the port. Please provide in the format ://:. Supported protocols are http and https. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + - name: repository + type: string + description: Name (ID) of the local repository on the ABAP system + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + - name: client + type: string + description: Client of the ABAP system in which you want to execute the checks + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + - name: aUnitTest + type: bool + default: true + description: Indication whether you want to execute the unit test checks. + scope: + - PARAMETERS + - STAGES + - STEPS + - name: atcCheck + type: bool + default: true + description: Indication whether you want to execute the ATC checks. + scope: + - PARAMETERS + - STAGES + - STEPS + - name: atcVariant + type: string + default: "DEFAULT_REMOTE_REF" + description: Variant for ATC checks + scope: + - PARAMETERS + - STAGES + - STEPS + - name: scope + type: string + default: repository + enum: + - localChangedObjects + - remoteChangedObjects + - localChangedPackages + - remoteChangedPackages + - repository + - packages + description: | + Scope of objects for which you want to execute the checks: + + * localChangedObjects - object delta derived from last activity in the local repository. The checks are executed for the individual objects. + * remoteChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the remote repository. The checks are executed for the individual objects. + * localChangedPackages - object delta derived from last activity in the local repository. All objects are resolved into packages. The checks are executed for the packages. + * remoteChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the remote repository. All objects are resolved into packages. The checks are executed for the packages. + * repository - all objects that are part of the local repository. The checks are executed for the individual objects. Packages (DEVC) are excluded. This is the default scope. + * packages - all packages that are part of the local repository . The checks are executed for the packages. + scope: + - PARAMETERS + - STAGES + - STEPS + - name: commit + type: string + description: ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes remoteChangedObjects and remoteChangedPackages secifying a commit it's mandatory. + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + - name: workspace + type: string + description: Absolute path to directory which contains the source code that your CI/CD tool checks out. For example in Jenkins, the workspace parameter is /var/jenkins_home/workspace// + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: true + - name: atcResultsFileName + type: string + description: Specifies output file name for the results from the ATC checks + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + default: "ATCResults.xml" + - name: aUnitResultsFileName + type: string + description: Specifies output file name for the results from the AUnit tests + scope: + - PARAMETERS + - STAGES + - STEPS + mandatory: false + default: "AUnitResults.xml" diff --git a/resources/metadata/gctsExecuteABAPUnitTests.yaml b/resources/metadata/gctsExecuteABAPUnitTests.yaml index 79300e3ff..299bc63c3 100644 --- a/resources/metadata/gctsExecuteABAPUnitTests.yaml +++ b/resources/metadata/gctsExecuteABAPUnitTests.yaml @@ -104,9 +104,9 @@ spec: description: | Scope of objects for which you want to execute the checks: - * localChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the local repository. The checks are executed for the individual objects. + * localChangedObjects - object delta derived from last activity in the local repository. The checks are executed for the individual objects. * remoteChangedObjects - object delta between the commit that triggered the pipeline and the current commit in the remote repository. The checks are executed for the individual objects. - * localChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the local repository. All objects are resolved into packages. The checks are executed for the packages. + * localChangedPackages - object delta derived from last activity in the local repository. All objects are resolved into packages. The checks are executed for the packages. * remoteChangedPackages - object delta between the commit that triggered the pipeline and the current commit in the remote repository. All objects are resolved into packages. The checks are executed for the packages. * repository - all objects that are part of the local repository. The checks are executed for the individual objects. Packages (DEVC) are excluded. This is the default scope. * packages - all packages that are part of the local repository . The checks are executed for the packages. @@ -116,7 +116,7 @@ spec: - STEPS - name: commit type: string - description: ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes localChangedObjects, remoteChangedObjects, localChangedPackages and remoteChangedPackages secifying a commit it's mandatory. + description: ID of the commit that triggered the pipeline or any other commit to compare objects. For scopes remoteChangedObjects and remoteChangedPackages secifying a commit it's mandatory. scope: - PARAMETERS - STAGES diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 774a34f33..719dbe6fc 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -168,6 +168,7 @@ public class CommonStepsTest extends BasePiperTest{ 'sonarExecuteScan', //implementing new golang pattern without fields 'gctsCreateRepository', //implementing new golang pattern without fields 'gctsRollback', //implementing new golang pattern without fields + 'gctsExecuteABAPQualityChecks', //implementing new golang pattern without fields 'gctsExecuteABAPUnitTests', //implementing new golang pattern without fields 'gctsCloneRepository', //implementing new golang pattern without fields 'fortifyExecuteScan', //implementing new golang pattern without fields diff --git a/vars/gctsExecuteABAPQualityChecks.groovy b/vars/gctsExecuteABAPQualityChecks.groovy new file mode 100644 index 000000000..a0a78adcb --- /dev/null +++ b/vars/gctsExecuteABAPQualityChecks.groovy @@ -0,0 +1,11 @@ +import groovy.transform.Field + +@Field String STEP_NAME = getClass().getName() +@Field String METADATA_FILE = 'metadata/gctsExecuteABAPQualityChecks.yaml' + +void call(Map parameters = [:]) { + List credentials = [ + [type: 'usernamePassword', id: 'abapCredentialsId', env: ['PIPER_username', 'PIPER_password']] + ] + piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials) +}