From b787c94e20df952dee9114a839ea31b9039b6893 Mon Sep 17 00:00:00 2001 From: Dragos Gabriel Ghinea <142506926+dgghinea@users.noreply.github.com> Date: Wed, 14 Feb 2024 19:27:27 +0200 Subject: [PATCH] APIGOV-27135 - mulesoft DA changes to parse RAML (#197) * APIGOV-27135 - minimal changes for raml support * APIGOV-27135 - add env var for raml + config tests * APIGOV-27135 - fix client test * APIGOV-27135 - MR issues * APIGOV-27135 - refactor GetExchangeFileContent + add tag if converted * APIGOV-27135 MR issues * APIGOV-27135 MR issues --- .gitignore | 2 + deployment/discovery-deployment.yaml | 2 + deployment/docker-compose.yml | 1 + go.mod | 2 +- go.sum | 2 + pkg/anypoint/client.go | 68 ++++--- pkg/anypoint/mocks.go | 4 +- pkg/anypoint/types.go | 3 +- pkg/config/config.go | 149 +++++++++------- pkg/config/config_test.go | 200 +++++++++++++++++++++ pkg/discovery/agent.go | 13 +- pkg/discovery/servicehandler.go | 113 +++++------- pkg/discovery/servicehandler_test.go | 256 +++++++++++---------------- pkg/traceability/file | 1 + 14 files changed, 497 insertions(+), 319 deletions(-) create mode 100644 pkg/config/config_test.go create mode 100644 pkg/traceability/file diff --git a/.gitignore b/.gitignore index a76b979..a62448a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ *.test *.out +file +__debug_bin** dist/ coverage/ **/local-values.yaml diff --git a/deployment/discovery-deployment.yaml b/deployment/discovery-deployment.yaml index 1e70990..52a2247 100644 --- a/deployment/discovery-deployment.yaml +++ b/deployment/discovery-deployment.yaml @@ -61,6 +61,8 @@ spec: value: - name: MULESOFT_POLLINTERVAL value: 20s + - name: MULESOFT_DISCOVERORIGINALRAML + value: false - name: STATUS_PORT value: "8989" image: diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index eb9d245..759a66c 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -23,6 +23,7 @@ services: MULESOFT_POLLINTERVAL: 20s MULESOFT_AUTH_USERNAME: MULESOFT_AUTH_PASSWORD: + MULESOFT_DISCOVERORIGINALRAML: false traceability: image: mulesoft-traceability # ports: diff --git a/go.mod b/go.mod index f534e90..c59165c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.21.3 //replace github.com/Axway/agent-sdk => /home/ubuntu/go/src/github.com/Axway/agent-sdk require ( - github.com/Axway/agent-sdk v1.1.73 + github.com/Axway/agent-sdk v1.1.74-0.20240131154839-8ccbdd686492 github.com/elastic/beats/v7 v7.17.17 github.com/getkin/kin-openapi v0.76.0 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 13adf42..275a3ba 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Axway/agent-sdk v1.1.73 h1:aLtVRDeWNz/bvlZrhDyYL7lWelWV0TsMqzHmsG2BUAY= github.com/Axway/agent-sdk v1.1.73/go.mod h1:CMuNRWCU4UswYhaR65bzLKEDT0xcViQwHJUGFCnH2yk= +github.com/Axway/agent-sdk v1.1.74-0.20240131154839-8ccbdd686492 h1:MtI5ZD3jSR3uYez39KNaUGILbmHUSPdIx8N0Llu4yqk= +github.com/Axway/agent-sdk v1.1.74-0.20240131154839-8ccbdd686492/go.mod h1:CMuNRWCU4UswYhaR65bzLKEDT0xcViQwHJUGFCnH2yk= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= diff --git a/pkg/anypoint/client.go b/pkg/anypoint/client.go index 6f4e3e2..ee5dfba 100644 --- a/pkg/anypoint/client.go +++ b/pkg/anypoint/client.go @@ -6,8 +6,9 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" + "strings" "time" "github.com/sirupsen/logrus" @@ -38,7 +39,7 @@ type Client interface { GetEnvironmentByName(name string) (*Environment, error) GetExchangeAsset(groupID, assetID, assetVersion string) (*ExchangeAsset, error) GetExchangeAssetIcon(icon string) (string, string, error) - GetExchangeFileContent(link, packaging, mainFile string) ([]byte, error) + GetExchangeFileContent(link, packaging, mainFile string, useOriginalRaml bool) ([]byte, bool, error) GetPolicies(apiID int64) ([]Policy, error) GetSLATiers(int642 int64) (*Tiers, error) ListAssets(page *Page) ([]Asset, error) @@ -296,11 +297,20 @@ func (c *AnypointClient) GetAPI(id string) (*API, error) { // GetPolicies lists the API policies. func (c *AnypointClient) GetPolicies(apiID int64) ([]Policy, error) { - policies := []Policy{} + policies := Policies{} url := fmt.Sprintf("%s/apimanager/api/v1/organizations/%s/environments/%s/apis/%d/policies", c.baseURL, c.auth.GetOrgID(), c.environment.ID, apiID) err := c.invokeJSONGet(url, nil, &policies, nil) - - return policies, err + // Older versions of mulesoft may return []Policy JSON format instead. + if err != nil && strings.HasPrefix(err.Error(), "json: cannot unmarshal") { + err = c.invokeJSONGet(url, nil, &(policies.Policies), nil) + } + // Same issue, but with ConfigurationData and Configuration + for i, pCfg := range policies.Policies { + if pCfg.ConfigurationData != nil { + policies.Policies[i].Configuration = pCfg.Configuration + } + } + return policies.Policies, err } // GetExchangeAsset creates the AssetDetail form the Asset API. @@ -333,33 +343,39 @@ func (c *AnypointClient) GetExchangeAssetIcon(icon string) (string, string, erro } // GetExchangeFileContent download the file from the ExternalLink reference. If the file is a zip file -// and thre is a MainFile set then the content of the MainFile is returned. -func (c *AnypointClient) GetExchangeFileContent(link, packaging, mainFile string) (fileContent []byte, err error) { - fileContent, _, err = c.invokeGet(link) +// and there is a MainFile set then the content of the MainFile is returned. +func (c *AnypointClient) GetExchangeFileContent(link, packaging, mainFile string, useOriginalRaml bool) ([]byte, bool, error) { + wasConverted := false + fileContent, _, err := c.invokeGet(link) + if packaging != "zip" { + return fileContent, wasConverted, err + } + zipReader, err := zip.NewReader(bytes.NewReader(fileContent), int64(len(fileContent))) + if err != nil { + return nil, wasConverted, err + } - if packaging == "zip" && mainFile != "" { - zipReader, err := zip.NewReader(bytes.NewReader(fileContent), int64(len(fileContent))) + for _, f := range zipReader.File { + // In case of RAML spec, this gets automatically converted and is renamed to api.json + if f.Name != mainFile && f.Name != "api.json" { + continue + } + content, err := f.Open() if err != nil { - return nil, err + return nil, wasConverted, err } - for _, f := range zipReader.File { - if f.Name == mainFile { - content, err := f.Open() - if err != nil { - return nil, err - } - - fileContent, err = ioutil.ReadAll(content) - content.Close() - if err != nil { - return nil, err - } - break - } + fileContent, err = io.ReadAll(content) + content.Close() + if err != nil { + return nil, wasConverted, err + } + if !useOriginalRaml && f.Name == "api.json" { + return fileContent, true, err } + break } - return fileContent, err + return fileContent, wasConverted, err } // GetAnalyticsWindow lists the managed assets in Mulesoft: https://docs.qax.mulesoft.com/api-manager/2.x/analytics-event-api diff --git a/pkg/anypoint/mocks.go b/pkg/anypoint/mocks.go index e11985f..e28244f 100644 --- a/pkg/anypoint/mocks.go +++ b/pkg/anypoint/mocks.go @@ -92,10 +92,10 @@ func (m *MockAnypointClient) GetExchangeAssetIcon(_ string) (string, string, err return icon, contentType, args.Error(2) } -func (m *MockAnypointClient) GetExchangeFileContent(_, _, _ string) ([]byte, error) { +func (m *MockAnypointClient) GetExchangeFileContent(_, _, _ string, shouldConvert bool) ([]byte, bool, error) { args := m.Called() result := args.Get(0) - return result.([]byte), args.Error(1) + return result.([]byte), shouldConvert, args.Error(2) } func (m *MockAnypointClient) GetAnalyticsWindow() ([]AnalyticsEvent, error) { diff --git a/pkg/anypoint/types.go b/pkg/anypoint/types.go index f26ec2f..4cb1f9d 100644 --- a/pkg/anypoint/types.go +++ b/pkg/anypoint/types.go @@ -98,7 +98,8 @@ type API struct { type Policy struct { // APIID int64 `json:"apiId,omitempty"` // Audit Audit `json:"audit,omitempty"` - Configuration interface{} `json:"configurationData,omitempty"` + Configuration interface{} `json:"configuration,omitempty"` + ConfigurationData interface{} `json:"configurationData,omitempty"` // ID int64 `json:"id,omitempty"` // MasterOrganizationID string `json:"masterOrganizationId,omitempty"` // Order int `json:"order,omitempty"` diff --git a/pkg/config/config.go b/pkg/config/config.go index f688762..14dafde 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,6 +11,17 @@ import ( corecfg "github.com/Axway/agent-sdk/pkg/config" ) +type props interface { + AddStringProperty(name string, defaultVal string, description string) + AddStringSliceProperty(name string, defaultVal []string, description string) + AddBoolProperty(name string, defaultVal bool, description string) + AddDurationProperty(name string, defaultVal time.Duration, description string, opts ...properties.DurationOpt) + StringPropertyValue(name string) string + StringSlicePropertyValue(name string) []string + BoolPropertyValue(name string) bool + DurationPropertyValue(name string) time.Duration +} + var config *AgentConfig const ( @@ -32,6 +43,19 @@ const ( pathPollInterval = "mulesoft.pollInterval" pathProxyURL = "mulesoft.proxyUrl" pathCachePath = "mulesoft.cachePath" + pathDiscoverOriginalRaml = "mulesoft.discoverOriginalRaml" +) + +const ( + anypointExchangeUrlErr = "invalid mulesoft configuration: anypointExchangeUrl is not configured" + usernameOrClientIDErr = "invalid mulesoft configuration: username or client id must be configured" + usernameAndClientIDErr = "invalid mulesoft configuration: cannot use both non-empty username and client id" + passwordErr = "invalid mulesoft configuration: password is not configured" + clientSecretErr = "invalid mulesoft configuration: client secret is not configured" + envErr = "invalid mulesoft configuration: environment is not configured" + orgNameErr = "invalid mulesoft configuration: OrgName is not configured" + pollIntervalErr = "invalid mulesoft configuration: pollInterval is invalid" + cachePathErr = "invalid mulesoft cache path: path does not exist: " ) // SetConfig sets the global AgentConfig reference. @@ -53,109 +77,112 @@ type AgentConfig struct { // MulesoftConfig - represents the config for the Mulesoft gateway type MulesoftConfig struct { corecfg.IConfigValidator - AnypointExchangeURL string `config:"anypointExchangeUrl"` - CachePath string `config:"cachePath"` - DiscoveryIgnoreTags string `config:"discoveryIgnoreTags"` - DiscoveryTags string `config:"discoveryTags"` - Environment string `config:"environment"` - OrgName string `config:"orgname"` - Password string `config:"auth.password"` - PollInterval time.Duration `config:"pollInterval"` - ProxyURL string `config:"proxyUrl"` - SessionLifetime time.Duration `config:"auth.lifetime"` - TLS corecfg.TLSConfig `config:"ssl"` - Username string `config:"auth.username"` - ClientID string `config:"auth.clientID"` - ClientSecret string `config:"auth.clientSecret"` + AnypointExchangeURL string `config:"anypointExchangeUrl"` + CachePath string `config:"cachePath"` + DiscoveryIgnoreTags string `config:"discoveryIgnoreTags"` + DiscoveryTags string `config:"discoveryTags"` + Environment string `config:"environment"` + OrgName string `config:"orgname"` + Password string `config:"auth.password"` + PollInterval time.Duration `config:"pollInterval"` + ProxyURL string `config:"proxyUrl"` + SessionLifetime time.Duration `config:"auth.lifetime"` + TLS corecfg.TLSConfig `config:"ssl"` + Username string `config:"auth.username"` + ClientID string `config:"auth.clientID"` + ClientSecret string `config:"auth.clientSecret"` + DiscoverOriginalRaml bool `config:"discoverOriginalRaml"` } // ValidateCfg - Validates the gateway config func (c *MulesoftConfig) ValidateCfg() (err error) { if c.AnypointExchangeURL == "" { - return errors.New("invalid mulesoft configuration: anypointExchangeUrl is not configured") + return errors.New(anypointExchangeUrlErr) } if c.Username == "" && c.ClientID == "" { - return errors.New("invalid mulesoft configuration: username or client id must be configured") + return errors.New(usernameOrClientIDErr) } if c.Username != "" && c.ClientID != "" { - return errors.New("invalid mulesoft configuration: both username or client id can not be configured") + return errors.New(usernameAndClientIDErr) } if c.Username != "" && c.Password == "" { - return errors.New("invalid mulesoft configuration: password is not configured") + return errors.New(passwordErr) } if c.ClientID != "" && c.ClientSecret == "" { - return errors.New("invalid mulesoft configuration: client secret is not configured") + return errors.New(clientSecretErr) } if c.Environment == "" { - return errors.New("invalid mulesoft configuration: environment is not configured") + return errors.New(envErr) } if c.OrgName == "" { - return errors.New("invalid mulesoft configuration: OrgName is not configured") + return errors.New(orgNameErr) } if c.PollInterval == 0 { - return errors.New("invalid mulesoft configuration: pollInterval is invalid") + return errors.New(pollIntervalErr) } if _, err := os.Stat(c.CachePath); os.IsNotExist(err) { - return fmt.Errorf("invalid mulesoft cache path: path does not exist: %s", c.CachePath) + return fmt.Errorf(cachePathErr + c.CachePath) } c.CachePath = filepath.Clean(c.CachePath) return } // AddConfigProperties - Adds the command properties needed for Mulesoft -func AddConfigProperties(props properties.Properties) { - props.AddStringProperty(pathAnypointExchangeURL, "https://anypoint.mulesoft.com", "Mulesoft Anypoint Exchange URL.") - props.AddStringProperty(pathEnvironment, "", "Mulesoft Anypoint environment.") - props.AddStringProperty(pathOrgName, "", "Mulesoft Anypoint Business Group.") - props.AddStringProperty(pathAuthUsername, "", "Mulesoft username.") - props.AddStringProperty(pathAuthPassword, "", "Mulesoft password.") - props.AddStringProperty(pathAuthClientID, "", "Mulesoft client id.") - props.AddStringProperty(pathAuthClientSecret, "", "Mulesoft client secret.") - props.AddDurationProperty(pathAuthLifetime, 60*time.Minute, "Mulesoft session lifetime.") - props.AddStringProperty(pathDiscoveryTags, "", "APIs containing any of these tags are selected for discovery.") - props.AddStringProperty(pathDiscoveryIgnoreTags, "", "APIs containing any of these tags are ignored. Takes precedence over "+pathDiscoveryIgnoreTags+".") - props.AddStringProperty(pathCachePath, "/tmp", "Mulesoft Cache Path") - props.AddDurationProperty(pathPollInterval, 20*time.Second, "The interval at which Mulesoft is checked for updates.", - properties.WithLowerLimit(20*time.Second)) +func AddConfigProperties(rootProps props) { + rootProps.AddStringProperty(pathAnypointExchangeURL, "https://anypoint.mulesoft.com", "Mulesoft Anypoint Exchange URL.") + rootProps.AddStringProperty(pathEnvironment, "", "Mulesoft Anypoint environment.") + rootProps.AddStringProperty(pathOrgName, "", "Mulesoft Anypoint Business Group.") + rootProps.AddStringProperty(pathAuthUsername, "", "Mulesoft username.") + rootProps.AddStringProperty(pathAuthPassword, "", "Mulesoft password.") + rootProps.AddStringProperty(pathAuthClientID, "", "Mulesoft client id.") + rootProps.AddStringProperty(pathAuthClientSecret, "", "Mulesoft client secret.") + rootProps.AddDurationProperty(pathAuthLifetime, 60*time.Minute, "Mulesoft session lifetime.") + rootProps.AddStringProperty(pathDiscoveryTags, "", "APIs containing any of these tags are selected for discovery.") + rootProps.AddStringProperty(pathDiscoveryIgnoreTags, "", "APIs containing any of these tags are ignored. Takes precedence over "+pathDiscoveryIgnoreTags+".") + rootProps.AddStringProperty(pathCachePath, "/tmp", "Mulesoft Cache Path") + rootProps.AddDurationProperty(pathPollInterval, 20*time.Second, "The interval at which Mulesoft is checked for updates.", properties.WithLowerLimit(20*time.Second)) + rootProps.AddStringProperty(pathProxyURL, "", "Proxy URL") // ssl properties and command flags - props.AddStringSliceProperty(pathSSLNextProtos, []string{}, "List of supported application level protocols, comma separated.") - props.AddBoolProperty(pathSSLInsecureSkipVerify, false, "Controls whether a client verifies the server's certificate chain and host name.") - props.AddStringSliceProperty(pathSSLCipherSuites, corecfg.TLSDefaultCipherSuitesStringSlice(), "List of supported cipher suites, comma separated.") - props.AddStringProperty(pathSSLMinVersion, corecfg.TLSDefaultMinVersionString(), "Minimum acceptable SSL/TLS protocol version.") - props.AddStringProperty(pathSSLMaxVersion, "0", "Maximum acceptable SSL/TLS protocol version.") + rootProps.AddStringSliceProperty(pathSSLNextProtos, []string{}, "List of supported application level protocols, comma separated.") + rootProps.AddBoolProperty(pathSSLInsecureSkipVerify, false, "Controls whether a client verifies the server's certificate chain and host name.") + rootProps.AddStringSliceProperty(pathSSLCipherSuites, corecfg.TLSDefaultCipherSuitesStringSlice(), "List of supported cipher suites, comma separated.") + rootProps.AddStringProperty(pathSSLMinVersion, corecfg.TLSDefaultMinVersionString(), "Minimum acceptable SSL/TLS protocol version.") + rootProps.AddStringProperty(pathSSLMaxVersion, "0", "Maximum acceptable SSL/TLS protocol version.") + rootProps.AddBoolProperty(pathDiscoverOriginalRaml, false, "If RAML API specs are discovered as RAML and not converted to OAS") } // NewMulesoftConfig - parse the props and create an Mulesoft Configuration structure -func NewMulesoftConfig(props properties.Properties) *MulesoftConfig { +func NewMulesoftConfig(rootProps props) *MulesoftConfig { return &MulesoftConfig{ - AnypointExchangeURL: props.StringPropertyValue(pathAnypointExchangeURL), - CachePath: props.StringPropertyValue(pathCachePath), - DiscoveryIgnoreTags: props.StringPropertyValue(pathDiscoveryIgnoreTags), - DiscoveryTags: props.StringPropertyValue(pathDiscoveryTags), - Environment: props.StringPropertyValue(pathEnvironment), - OrgName: props.StringPropertyValue(pathOrgName), - Password: props.StringPropertyValue(pathAuthPassword), - PollInterval: props.DurationPropertyValue(pathPollInterval), - ProxyURL: props.StringPropertyValue(pathProxyURL), - SessionLifetime: props.DurationPropertyValue(pathAuthLifetime), - Username: props.StringPropertyValue(pathAuthUsername), - ClientID: props.StringPropertyValue(pathAuthClientID), - ClientSecret: props.StringPropertyValue(pathAuthClientSecret), + AnypointExchangeURL: rootProps.StringPropertyValue(pathAnypointExchangeURL), + CachePath: rootProps.StringPropertyValue(pathCachePath), + DiscoveryIgnoreTags: rootProps.StringPropertyValue(pathDiscoveryIgnoreTags), + DiscoveryTags: rootProps.StringPropertyValue(pathDiscoveryTags), + Environment: rootProps.StringPropertyValue(pathEnvironment), + OrgName: rootProps.StringPropertyValue(pathOrgName), + Password: rootProps.StringPropertyValue(pathAuthPassword), + PollInterval: rootProps.DurationPropertyValue(pathPollInterval), + ProxyURL: rootProps.StringPropertyValue(pathProxyURL), + SessionLifetime: rootProps.DurationPropertyValue(pathAuthLifetime), + Username: rootProps.StringPropertyValue(pathAuthUsername), + ClientID: rootProps.StringPropertyValue(pathAuthClientID), + ClientSecret: rootProps.StringPropertyValue(pathAuthClientSecret), TLS: &corecfg.TLSConfiguration{ - NextProtos: props.StringSlicePropertyValue(pathSSLNextProtos), - InsecureSkipVerify: props.BoolPropertyValue(pathSSLInsecureSkipVerify), - CipherSuites: corecfg.NewCipherArray(props.StringSlicePropertyValue(pathSSLCipherSuites)), - MinVersion: corecfg.TLSVersionAsValue(props.StringPropertyValue(pathSSLMinVersion)), - MaxVersion: corecfg.TLSVersionAsValue(props.StringPropertyValue(pathSSLMaxVersion)), + NextProtos: rootProps.StringSlicePropertyValue(pathSSLNextProtos), + InsecureSkipVerify: rootProps.BoolPropertyValue(pathSSLInsecureSkipVerify), + CipherSuites: corecfg.NewCipherArray(rootProps.StringSlicePropertyValue(pathSSLCipherSuites)), + MinVersion: corecfg.TLSVersionAsValue(rootProps.StringPropertyValue(pathSSLMinVersion)), + MaxVersion: corecfg.TLSVersionAsValue(rootProps.StringPropertyValue(pathSSLMaxVersion)), }, + DiscoverOriginalRaml: rootProps.BoolPropertyValue(pathDiscoverOriginalRaml), } } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 0000000..158ee84 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,200 @@ +package config + +import ( + "testing" + "time" + + "github.com/Axway/agent-sdk/pkg/cmd/properties" + corecfg "github.com/Axway/agent-sdk/pkg/config" + "github.com/stretchr/testify/assert" +) + +func TestKongGatewayCfg(t *testing.T) { + cfg := &MulesoftConfig{} + + err := cfg.ValidateCfg() + assert.Equal(t, anypointExchangeUrlErr, err.Error()) + + cfg.AnypointExchangeURL = "test.com" + err = cfg.ValidateCfg() + assert.Equal(t, usernameOrClientIDErr, err.Error()) + + cfg.Username = "Tom" + cfg.ClientID = "Jerry" + err = cfg.ValidateCfg() + assert.Equal(t, usernameAndClientIDErr, err.Error()) + + cfg.ClientID = "" + err = cfg.ValidateCfg() + assert.Equal(t, passwordErr, err.Error()) + + cfg.ClientID = "Jerry" + cfg.Username = "" + err = cfg.ValidateCfg() + assert.Equal(t, clientSecretErr, err.Error()) + + cfg.ClientSecret = "Spike" + err = cfg.ValidateCfg() + assert.Equal(t, envErr, err.Error()) + + cfg.Environment = "Backyard" + err = cfg.ValidateCfg() + assert.Equal(t, orgNameErr, err.Error()) + + cfg.OrgName = "Warner Bros" + err = cfg.ValidateCfg() + assert.Equal(t, pollIntervalErr, err.Error()) + + cfg.PollInterval = 20 * time.Minute + err = cfg.ValidateCfg() + assert.Equal(t, cachePathErr, err.Error()) + + cfg.CachePath = "./" + err = cfg.ValidateCfg() + assert.Equal(t, nil, err) + +} + +type propData struct { + pType string + desc string + val interface{} +} + +type fakeProps struct { + props map[string]propData +} + +func (f *fakeProps) AddStringProperty(name string, defaultVal string, description string) { + f.props[name] = propData{"string", description, defaultVal} +} + +func (f *fakeProps) AddStringSliceProperty(name string, defaultVal []string, description string) { + f.props[name] = propData{"string", description, defaultVal} +} + +func (f *fakeProps) AddDurationProperty(name string, defaultVal time.Duration, description string, opts ...properties.DurationOpt) { + f.props[name] = propData{"duration", description, defaultVal} +} + +func (f *fakeProps) AddBoolProperty(name string, defaultVal bool, description string) { + f.props[name] = propData{"bool", description, defaultVal} +} + +func (f *fakeProps) StringPropertyValue(name string) string { + if prop, ok := f.props[name]; ok { + return prop.val.(string) + } + return "" +} + +func (f *fakeProps) StringSlicePropertyValue(name string) []string { + if prop, ok := f.props[name]; ok { + return prop.val.([]string) + } + return []string{} +} + +func (f *fakeProps) DurationPropertyValue(name string) time.Duration { + if prop, ok := f.props[name]; ok { + return prop.val.(time.Duration) + } + return 0 +} + +func (f *fakeProps) BoolPropertyValue(name string) bool { + if prop, ok := f.props[name]; ok { + return prop.val.(bool) + } + return false +} + +func TestKongProperties(t *testing.T) { + newProps := &fakeProps{props: map[string]propData{}} + + // validate add props + AddConfigProperties(newProps) + assert.Contains(t, newProps.props, pathAnypointExchangeURL) + assert.Contains(t, newProps.props, pathEnvironment) + assert.Contains(t, newProps.props, pathOrgName) + assert.Contains(t, newProps.props, pathDiscoveryTags) + assert.Contains(t, newProps.props, pathDiscoveryIgnoreTags) + assert.Contains(t, newProps.props, pathAuthUsername) + assert.Contains(t, newProps.props, pathAuthPassword) + assert.Contains(t, newProps.props, pathAuthClientID) + assert.Contains(t, newProps.props, pathAuthClientSecret) + assert.Contains(t, newProps.props, pathAuthLifetime) + assert.Contains(t, newProps.props, pathSSLNextProtos) + assert.Contains(t, newProps.props, pathSSLInsecureSkipVerify) + assert.Contains(t, newProps.props, pathSSLCipherSuites) + assert.Contains(t, newProps.props, pathSSLMinVersion) + assert.Contains(t, newProps.props, pathSSLMaxVersion) + assert.Contains(t, newProps.props, pathPollInterval) + assert.Contains(t, newProps.props, pathProxyURL) + assert.Contains(t, newProps.props, pathCachePath) + assert.Contains(t, newProps.props, pathDiscoverOriginalRaml) + + // validate defaults + cfg := NewMulesoftConfig(newProps) + assert.Equal(t, "https://anypoint.mulesoft.com", cfg.AnypointExchangeURL) + assert.Equal(t, "", cfg.Environment) + assert.Equal(t, "", cfg.OrgName) + assert.Equal(t, "", cfg.DiscoveryTags) + assert.Equal(t, "", cfg.DiscoveryIgnoreTags) + assert.Equal(t, "", cfg.Username) + assert.Equal(t, "", cfg.Password) + assert.Equal(t, "", cfg.ClientID) + assert.Equal(t, "", cfg.ClientSecret) + assert.Equal(t, 60*time.Minute, cfg.SessionLifetime) + assert.Equal(t, []string{}, cfg.TLS.GetNextProtos()) + assert.Equal(t, false, cfg.TLS.IsInsecureSkipVerify()) + assert.Equal(t, corecfg.NewCipherArray(corecfg.TLSDefaultCipherSuitesStringSlice()), cfg.TLS.GetCipherSuites()) + assert.Equal(t, corecfg.TLSVersionAsValue(corecfg.TLSDefaultMinVersionString()), cfg.TLS.GetMinVersion()) + assert.Equal(t, corecfg.TLSVersionAsValue("0"), cfg.TLS.GetMaxVersion()) + assert.Equal(t, 20*time.Second, cfg.PollInterval) + assert.Equal(t, "", cfg.ProxyURL) + assert.Equal(t, "/tmp", cfg.CachePath) + assert.Equal(t, false, cfg.DiscoverOriginalRaml) + + // validate changed values + newProps.props[pathAnypointExchangeURL] = propData{"string", "", "ok.com"} + newProps.props[pathEnvironment] = propData{"string", "", "env"} + newProps.props[pathOrgName] = propData{"string", "", "orgName"} + newProps.props[pathDiscoveryTags] = propData{"string", "", "tag1"} + newProps.props[pathDiscoveryIgnoreTags] = propData{"string", "", "tag-ignore"} + newProps.props[pathAuthUsername] = propData{"string", "", "username"} + newProps.props[pathAuthPassword] = propData{"string", "", "password"} + newProps.props[pathAuthClientID] = propData{"string", "", "clientID"} + newProps.props[pathAuthClientSecret] = propData{"string", "", "clientSecret"} + newProps.props[pathAuthLifetime] = propData{"duration", "", time.Minute * 20} + newProps.props[pathSSLNextProtos] = propData{"[]string", "", []string{"sslNextProtos1", "sslNextProtos2"}} + newProps.props[pathSSLInsecureSkipVerify] = propData{"bool", "", true} + newProps.props[pathSSLCipherSuites] = propData{"[]string", "", []string{"ECDHE-ECDSA-AES-128-CBC-SHA", "ECDHE-ECDSA-AES-128-CBC-SHA256", "ECDHE-ECDSA-AES-128-GCM-SHA256"}} + newProps.props[pathSSLMinVersion] = propData{"string", "", "TLS1.0"} + newProps.props[pathSSLMaxVersion] = propData{"string", "", "TLS1.2"} + newProps.props[pathPollInterval] = propData{"duration", "", time.Minute * 20} + newProps.props[pathProxyURL] = propData{"string", "", "proxy.ok.com"} + newProps.props[pathCachePath] = propData{"string", "", "./config"} + newProps.props[pathDiscoverOriginalRaml] = propData{"bool", "", true} + + cfg = NewMulesoftConfig(newProps) + assert.Equal(t, "ok.com", cfg.AnypointExchangeURL) + assert.Equal(t, "env", cfg.Environment) + assert.Equal(t, "orgName", cfg.OrgName) + assert.Equal(t, "tag1", cfg.DiscoveryTags) + assert.Equal(t, "tag-ignore", cfg.DiscoveryIgnoreTags) + assert.Equal(t, "username", cfg.Username) + assert.Equal(t, "password", cfg.Password) + assert.Equal(t, "clientID", cfg.ClientID) + assert.Equal(t, "clientSecret", cfg.ClientSecret) + assert.Equal(t, time.Minute*20, cfg.SessionLifetime) + assert.Equal(t, []string{"sslNextProtos1", "sslNextProtos2"}, cfg.TLS.GetNextProtos()) + assert.Equal(t, true, cfg.TLS.IsInsecureSkipVerify()) + assert.Equal(t, corecfg.NewCipherArray([]string{"ECDHE-ECDSA-AES-128-CBC-SHA", "ECDHE-ECDSA-AES-128-CBC-SHA256", "ECDHE-ECDSA-AES-128-GCM-SHA256"}), cfg.TLS.GetCipherSuites()) + assert.Equal(t, corecfg.TLSVersionAsValue("TLS1.0"), cfg.TLS.GetMinVersion()) + assert.Equal(t, corecfg.TLSVersionAsValue("TLS1.2"), cfg.TLS.GetMaxVersion()) + assert.Equal(t, time.Minute*20, cfg.PollInterval) + assert.Equal(t, "proxy.ok.com", cfg.ProxyURL) + assert.Equal(t, "./config", cfg.CachePath) + assert.Equal(t, true, cfg.DiscoverOriginalRaml) +} diff --git a/pkg/discovery/agent.go b/pkg/discovery/agent.go index 9dacd54..d2de31e 100644 --- a/pkg/discovery/agent.go +++ b/pkg/discovery/agent.go @@ -43,12 +43,13 @@ func NewAgent(cfg *config.AgentConfig, client anypoint.Client, sm subscription.S c := cache.New() svcHandler := &serviceHandler{ - muleEnv: cfg.MulesoftConfig.Environment, - discoveryTags: cleanTags(cfg.MulesoftConfig.DiscoveryTags), - discoveryIgnoreTags: cleanTags(cfg.MulesoftConfig.DiscoveryIgnoreTags), - client: client, - schemas: sm, - cache: c, + muleEnv: cfg.MulesoftConfig.Environment, + discoveryTags: cleanTags(cfg.MulesoftConfig.DiscoveryTags), + discoveryIgnoreTags: cleanTags(cfg.MulesoftConfig.DiscoveryIgnoreTags), + client: client, + schemas: sm, + cache: c, + discoverOriginalRaml: cfg.MulesoftConfig.DiscoverOriginalRaml, } if util.IsNil(sm) { diff --git a/pkg/discovery/servicehandler.go b/pkg/discovery/servicehandler.go index 06ec631..0565cbc 100644 --- a/pkg/discovery/servicehandler.go +++ b/pkg/discovery/servicehandler.go @@ -12,6 +12,7 @@ import ( "github.com/Axway/agent-sdk/pkg/cache" "github.com/Axway/agent-sdk/pkg/util/oas" "github.com/Axway/agents-mulesoft/pkg/common" + "gopkg.in/yaml.v2" "github.com/sirupsen/logrus" @@ -25,7 +26,6 @@ import ( "github.com/Axway/agent-sdk/pkg/apic" "github.com/Axway/agents-mulesoft/pkg/anypoint" "github.com/Axway/agents-mulesoft/pkg/config" - "sigs.k8s.io/yaml" ) const ( @@ -40,13 +40,14 @@ type ServiceHandler interface { } type serviceHandler struct { - muleEnv string - discoveryTags []string - discoveryIgnoreTags []string - client anypoint.Client - schemas subs.SchemaStore - cache cache.Cache - mode string + muleEnv string + discoveryTags []string + discoveryIgnoreTags []string + client anypoint.Client + schemas subs.SchemaStore + cache cache.Cache + mode string + discoverOriginalRaml bool } func (s *serviceHandler) OnConfigChange(cfg *config.MulesoftConfig) { @@ -159,28 +160,25 @@ func (s *serviceHandler) getServiceDetail(asset *anypoint.Asset, api *anypoint.A return nil, err } - exchFile := getExchangeAssetSpecFile(exchangeAsset.Files) + exchFile := getExchangeAssetSpecFile(exchangeAsset.Files, s.discoverOriginalRaml) if exchFile == nil { logger.Debugf("no supported specification file found") return nil, nil } - rawSpec, err := s.client.GetExchangeFileContent(exchFile.ExternalLink, exchFile.Packaging, exchFile.MainFile) + rawSpec, wasConverted, err := s.client.GetExchangeFileContent(exchFile.ExternalLink, exchFile.Packaging, exchFile.MainFile, s.discoverOriginalRaml) if err != nil { return nil, err } - - specContent := specYAMLToJSON(rawSpec) // SDK does not support YAML specifications - specType, err := getSpecType(exchFile, specContent) - if err != nil { - return nil, err + if wasConverted { + api.Tags = append(api.Tags, "converted-from-raml") } - if specType == "" { - return nil, fmt.Errorf("unknown spec type") - } + parser := apic.NewSpecResourceParser(rawSpec, "") + parser.Parse() + processor := parser.GetSpecProcessor() - modifiedSpec, err := updateSpec(specType, api.EndpointURI, authPolicy, configuration, specContent) + modifiedSpec, err := updateSpec(processor.GetResourceType(), api.EndpointURI, authPolicy, configuration, processor.GetSpecBytes()) if err != nil { return nil, err } @@ -206,7 +204,7 @@ func (s *serviceHandler) getServiceDetail(asset *anypoint.Asset, api *anypoint.A ID: fmt.Sprint(asset.ID), Image: icon, ImageContentType: iconContentType, - ResourceType: specType, + ResourceType: processor.GetResourceType(), ServiceAttributes: map[string]string{}, AgentDetails: map[string]string{ common.AttrAssetID: fmt.Sprint(asset.ID), @@ -290,7 +288,6 @@ func updateSpec( logrus.Debugf("failed to update the spec with the given endpoint: %s", endpointURI) } specContent, err = setOAS2policies(oas2Swagger, authPolicy, configuration) - case apic.Oas3: oas3Swagger, err = oas.ParseOAS3(specContent) if err != nil { @@ -298,21 +295,24 @@ func updateSpec( } oas.SetOAS3Servers([]string{endpointURI}, oas3Swagger) specContent, err = setOAS3policies(oas3Swagger, authPolicy, configuration) - - case apic.Wsdl: - specContent, err = setWSDLEndpoint(endpointURI, specContent) + case apic.Raml: + specContent, err = setRamlEndpoints(specContent, endpointURI) } return specContent, err } // getExchangeAssetSpecFile gets the file entry for the Assets spec. -func getExchangeAssetSpecFile(exchangeFiles []anypoint.ExchangeFile) *anypoint.ExchangeFile { +func getExchangeAssetSpecFile(exchangeFiles []anypoint.ExchangeFile, discoverOriginalRaml bool) *anypoint.ExchangeFile { if len(exchangeFiles) == 0 { return nil } - sort.Sort(BySpecType(exchangeFiles)) + + if discoverOriginalRaml { + return getExchangeAssetWithRamlSpecFile(exchangeFiles) + } + // By default, the RAML spec will have a download link with it as already converted to OAS but have an empty MainFile field if exchangeFiles[0].Classifier != "oas" && exchangeFiles[0].Classifier != "fat-oas" && exchangeFiles[0].Classifier != "wsdl" { @@ -322,48 +322,15 @@ func getExchangeAssetSpecFile(exchangeFiles []anypoint.ExchangeFile) *anypoint.E return &exchangeFiles[0] } -// specYAMLToJSON - if the spec is yaml convert it to json, SDK doesn't handle yaml. -func specYAMLToJSON(specContent []byte) []byte { - specMap := make(map[string]interface{}) - // check if the content is already json - err := json.Unmarshal(specContent, &specMap) - if err == nil { - return specContent - } - - // check if the content is already yaml - err = yaml.Unmarshal(specContent, &specMap) - if err != nil { - // Not yaml, nothing more to be done - return specContent - } - - bts, err := yaml.YAMLToJSON(specContent) - if err != nil { - return specContent - } - return bts -} - -// getSpecType determines the correct resource type for the asset. -func getSpecType(file *anypoint.ExchangeFile, specContent []byte) (string, error) { - if file.Classifier == apic.Wsdl { - return apic.Wsdl, nil - } - - if specContent != nil { - jsonMap := make(map[string]interface{}) - err := json.Unmarshal(specContent, &jsonMap) - if err != nil { - return "", err - } - if _, isSwagger := jsonMap["swagger"]; isSwagger { - return apic.Oas2, nil - } else if _, isOpenAPI := jsonMap["openapi"]; isOpenAPI { - return apic.Oas3, nil +func getExchangeAssetWithRamlSpecFile(exchangeFiles []anypoint.ExchangeFile) *anypoint.ExchangeFile { + for i := range exchangeFiles { + c := exchangeFiles[i].Classifier + if _, found := specPreference[c]; found && exchangeFiles[i].MainFile != "" { + return &exchangeFiles[i] } } - return "", nil + // Unsupported spec type + return nil } // getAuthPolicy gets the authentication policy type. @@ -393,8 +360,18 @@ func getAuthPolicy(policies []anypoint.Policy, mode string) (string, map[string] return apic.Passthrough, map[string]interface{}{}, false } -func setWSDLEndpoint(_ string, specContent []byte) ([]byte, error) { - return specContent, nil +func setRamlEndpoints(spec []byte, endpoints string) ([]byte, error) { + var ramlDef map[string]interface{} + // We know that this is a valid raml file from the parser, so this is never fails. We need this because yaml unmarshal drops the version line + ramlVersion := append(spec[0:10], []byte("\n")...) + yaml.Unmarshal(spec, &ramlDef) + ramlDef["baseUri"] = endpoints + + modifiedSpec, err := yaml.Marshal(ramlDef) + if err != nil { + return spec, err + } + return append(ramlVersion, modifiedSpec...), nil } // makeChecksum generates a makeChecksum for the api for change detection diff --git a/pkg/discovery/servicehandler_test.go b/pkg/discovery/servicehandler_test.go index a46030d..6b28155 100644 --- a/pkg/discovery/servicehandler_test.go +++ b/pkg/discovery/servicehandler_test.go @@ -56,59 +56,82 @@ var exchangeAsset = anypoint.ExchangeAsset{ } func TestServiceHandler(t *testing.T) { - content := `{"openapi":"3.0.1","servers":[{"url":"https://abc.com"}], "paths":{}, "info":{"title":"petstore3"}}` - policies := []anypoint.Policy{ + type testCase struct { + content string + policies []anypoint.Policy + exchangeAsset *anypoint.ExchangeAsset + exchangeAssetIcon string + expectedResourceType string + expectedTitle string + expectedVersion string + } + cases := []testCase{ { - PolicyTemplateID: common.ClientIDEnforcement, + content: "#%RAML 1.0\ntitle: API with Examples\ndescription: Grand Theft Auto:Vice City\nversion: v3\nprotocols: [HTTP,HTTPS]\nbaseUri: https://na1.salesforce.com:4000/services/data/{version}/chatter", + policies: []anypoint.Policy{ + {PolicyTemplateID: common.ClientIDEnforcement}, + }, + exchangeAsset: &exchangeAsset, + expectedResourceType: apic.Raml, + }, + { + content: `{"openapi":"3.0.1","servers":[{"url":"https://abc.com"}], "paths":{}, "info":{"title":"petstore3"}}`, + policies: []anypoint.Policy{ + {PolicyTemplateID: common.ClientIDEnforcement}, + }, + exchangeAsset: &exchangeAsset, + expectedResourceType: apic.Oas3, }, } - mc := &anypoint.MockAnypointClient{} - mc.On("GetPolicies").Return(policies, nil) - mc.On("GetExchangeAsset").Return(&exchangeAsset, nil) - mc.On("GetExchangeFileContent").Return([]byte(content), nil) - mc.On("GetExchangeAssetIcon").Return("", "", nil) - - msh := &mockSchemaHandler{} - sh := &serviceHandler{ - muleEnv: "Sandbox", - discoveryTags: []string{"tag1"}, - discoveryIgnoreTags: []string{"nah"}, - client: mc, - schemas: msh, - cache: cache.New(), + for _, c := range cases { + mc := &anypoint.MockAnypointClient{} + mc.On("GetPolicies").Return(c.policies, nil) + mc.On("GetExchangeAsset").Return(c.exchangeAsset, nil) + mc.On("GetExchangeFileContent").Return([]byte(c.content), true, nil) + mc.On("GetExchangeAssetIcon").Return("", "", nil) + + msh := &mockSchemaHandler{} + sh := &serviceHandler{ + muleEnv: "Sandbox", + discoveryTags: []string{"tag1"}, + discoveryIgnoreTags: []string{"nah"}, + client: mc, + schemas: msh, + cache: cache.New(), + } + list := sh.ToServiceDetails(&asset) + api := asset.APIs[0] + assert.Equal(t, 1, len(list)) + item := list[0] + + assert.Equal(t, asset.APIs[0].AssetID, item.APIName) + assert.Equal(t, apic.Apikey, item.AuthPolicy) + assert.Equal(t, fmt.Sprint(asset.ID), item.ID) + assert.Equal(t, c.expectedResourceType, item.ResourceType) + assert.Equal(t, api.AssetVersion, item.Stage) + assert.Equal(t, asset.ExchangeAssetName, item.Title) + assert.Equal(t, api.AssetVersion, item.Version) + assert.Equal(t, api.Tags, item.Tags) + assert.NotEmpty(t, item.AgentDetails[common.AttrChecksum]) + assert.Equal(t, fmt.Sprint(api.ID), item.AgentDetails[common.AttrAPIID]) + assert.Equal(t, fmt.Sprint(asset.ID), item.AgentDetails[common.AttrAssetID]) + assert.Equal(t, api.AssetVersion, item.AgentDetails[common.AttrAssetVersion]) + assert.Equal(t, api.ProductVersion, item.AgentDetails[common.AttrProductVersion]) + + // Should find the api in the cache + cachedItem, err := sh.cache.Get(item.AgentDetails[common.AttrChecksum]) + assert.Nil(t, err) + assert.Equal(t, api, cachedItem) + + // Should find the api in the cache by the secondary key + cachedItem, err = sh.cache.GetBySecondaryKey(common.FormatAPICacheKey(fmt.Sprint(api.ID), api.ProductVersion)) + assert.Nil(t, err) + assert.Equal(t, api, cachedItem) + + // Should not discover an API that is saved in the cache. + list = sh.ToServiceDetails(&asset) + assert.Equal(t, 0, len(list)) } - - list := sh.ToServiceDetails(&asset) - api := asset.APIs[0] - assert.Equal(t, 1, len(list)) - item := list[0] - assert.Equal(t, asset.APIs[0].AssetID, item.APIName) - assert.Equal(t, apic.Apikey, item.AuthPolicy) - assert.Equal(t, fmt.Sprint(asset.ID), item.ID) - assert.Equal(t, apic.Oas3, item.ResourceType) - assert.Equal(t, api.AssetVersion, item.Stage) - assert.Equal(t, asset.ExchangeAssetName, item.Title) - assert.Equal(t, api.AssetVersion, item.Version) - assert.Equal(t, api.Tags, item.Tags) - assert.NotEmpty(t, item.AgentDetails[common.AttrChecksum]) - assert.Equal(t, fmt.Sprint(api.ID), item.AgentDetails[common.AttrAPIID]) - assert.Equal(t, fmt.Sprint(asset.ID), item.AgentDetails[common.AttrAssetID]) - assert.Equal(t, api.AssetVersion, item.AgentDetails[common.AttrAssetVersion]) - assert.Equal(t, api.ProductVersion, item.AgentDetails[common.AttrProductVersion]) - - // Should find the api in the cache - cachedItem, err := sh.cache.Get(item.AgentDetails[common.AttrChecksum]) - assert.Nil(t, err) - assert.Equal(t, api, cachedItem) - - // Should find the api in the cache by the secondary key - cachedItem, err = sh.cache.GetBySecondaryKey(common.FormatAPICacheKey(fmt.Sprint(api.ID), api.ProductVersion)) - assert.Nil(t, err) - assert.Equal(t, api, cachedItem) - - // Should not discover an API that is saved in the cache. - list = sh.ToServiceDetails(&asset) - assert.Equal(t, 0, len(list)) } func TestServiceHandlerSLAPolicy(t *testing.T) { @@ -125,7 +148,7 @@ func TestServiceHandlerSLAPolicy(t *testing.T) { mc := &anypoint.MockAnypointClient{} mc.On("GetPolicies").Return(policies, nil) mc.On("GetExchangeAsset").Return(&exchangeAsset, nil) - mc.On("GetExchangeFileContent").Return([]byte(content), nil) + mc.On("GetExchangeFileContent").Return([]byte(content), true, nil) mc.On("GetExchangeAssetIcon").Return("", "", nil) msh := &mockSchemaHandler{} @@ -284,9 +307,10 @@ func TestShouldDiscoverAPIBasedOnTags(t *testing.T) { func TestGetExchangeAssetSpecFile(t *testing.T) { tests := []struct { - name string - files []anypoint.ExchangeFile - expected *anypoint.ExchangeFile + name string + files []anypoint.ExchangeFile + expected *anypoint.ExchangeFile + discoverOriginalRaml bool }{ { name: "Should return nil if the Exchange asset has no files", @@ -337,11 +361,31 @@ func TestGetExchangeAssetSpecFile(t *testing.T) { Classifier: "oas", }, }, + { + name: "Should sort files and return first non-empty mainFile", + files: []anypoint.ExchangeFile{ + { + Classifier: "wsdl", + }, + { + Classifier: "oas", + }, + { + Classifier: "raml", + MainFile: "1", + }, + }, + expected: &anypoint.ExchangeFile{ + Classifier: "raml", + MainFile: "1", + }, + discoverOriginalRaml: true, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - sd := getExchangeAssetSpecFile(tc.files) + sd := getExchangeAssetSpecFile(tc.files, tc.discoverOriginalRaml) assert.Equal(t, tc.expected, sd) }) } @@ -418,102 +462,6 @@ func Test_getAuthPolicy(t *testing.T) { } } -func Test_getSpecType(t *testing.T) { - tests := []struct { - name string - file *anypoint.ExchangeFile - specContent []byte - expectedType string - expectedErr error - }{ - { - name: "should return the spec type as WSDL", - file: &anypoint.ExchangeFile{ - Classifier: apic.Wsdl, - }, - specContent: []byte(""), - expectedType: apic.Wsdl, - }, - { - name: "should return the spec type as OAS2", - file: &anypoint.ExchangeFile{ - Classifier: apic.Oas2, - }, - specContent: []byte(`{"basePath":"google.com","host":"","schemes":[""],"swagger":"2.0"}`), - expectedType: apic.Oas2, - }, - { - name: "should return the spec type as OAS3", - file: &anypoint.ExchangeFile{ - Classifier: apic.Oas3, - }, - specContent: []byte(`{"openapi": "3.0.1"}`), - expectedType: apic.Oas3, - }, - { - name: "should return the specType as an empty string when the specContent is nil", - file: &anypoint.ExchangeFile{ - Classifier: apic.Oas3, - }, - specContent: nil, - expectedType: "", - }, - { - name: "should return an error when given an invalid spec", - file: &anypoint.ExchangeFile{ - Classifier: apic.Oas3, - }, - specContent: []byte("abc"), - expectedType: "", - expectedErr: fmt.Errorf("error"), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - specType, err := getSpecType(tc.file, tc.specContent) - if tc.expectedErr != nil { - assert.NotNil(t, err) - } else { - assert.Nil(t, err) - } - assert.Equal(t, tc.expectedType, specType) - }) - } -} - -func Test_specYAMLToJSON(t *testing.T) { - tests := []struct { - name string - input string - output []byte - }{ - { - name: "should convert yaml to json", - input: `--- -openapi: 3.0.1 -`, - output: []byte(`{"openapi":"3.0.1"}`), - }, - { - name: "should return the content when it is already json", - input: `{"openapi":"3.0.1"}`, - output: []byte(`{"openapi":"3.0.1"}`), - }, - { - name: "should return the content when it is not yaml or json", - input: `nope`, - output: []byte(`nope`), - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - res := specYAMLToJSON([]byte(tc.input)) - assert.Equal(t, tc.output, res) - }) - } -} - func Test_updateSpec(t *testing.T) { tests := []struct { name string @@ -556,11 +504,11 @@ func Test_updateSpec(t *testing.T) { authPolicy: apic.Apikey, }, { - name: "should update a WSDL spec", - specType: apic.Wsdl, + name: "should update a Raml spec", + specType: apic.Raml, endpoint: "https://abc.com", - content: []byte(""), - expectedContent: []byte(""), + content: []byte("#%RAML 1.0\nbaseUri: https://na1.salesforce.com:4000/services/data/{version}/chatter"), + expectedContent: []byte("#%RAML 1.0\nbaseUri: https://abc.com\n"), }, } for _, tc := range tests { diff --git a/pkg/traceability/file b/pkg/traceability/file new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/pkg/traceability/file @@ -0,0 +1 @@ +1 \ No newline at end of file