diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6845e8f30..fee8b256d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,6 +37,28 @@ jobs: - uses: actions/checkout@v3 - run: make podman-build + test-unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: '1.19' + - run: make test + + #test-api: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: actions/setup-go@v3 + # with: + # go-version: '1.19' + # - run: | + # make vet + # make run & + # sleep 15 # probably a dirty solution + # HUB_BASE_URL=http://localhost:8080 make test-api + test-e2e: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test-nightly.yml b/.github/workflows/test-nightly.yml new file mode 100644 index 000000000..1739ce78f --- /dev/null +++ b/.github/workflows/test-nightly.yml @@ -0,0 +1,32 @@ +name: Test nightly + +on: + schedule: + - cron: '13 0,12 * * *' # Regulary every 12 hours + +jobs: + test-integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Start minikube + uses: konveyor/tackle2-operator/.github/actions/start-minikube@main + - name: Build image in minikube + run: | + export SHELL=/bin/bash + eval $(minikube -p minikube docker-env) + make docker-build + - name: Install Tackle + uses: konveyor/tackle2-operator/.github/actions/install-tackle@main + with: + tackle-hub-image: tackle2-hub:latest + tackle-image-pull-policy: IfNotPresent + - name: Set host and namespace + run: | + echo "host=$(minikube ip)/hub" >> $GITHUB_ENV + echo "namespace=$(kubectl get tackles.tackle.konveyor.io --all-namespaces --no-headers | awk '{print $1}')" >> $GITHUB_ENV + - name: Test execution + run: | + HUB_BASE_URL="http://$(minikube ip)/hub" make test-integration + with: + host: ${{ env.host }} diff --git a/Makefile b/Makefile index 74f98f492..492e14c98 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GOBIN ?= ${GOPATH}/bin IMG ?= tackle2-hub:latest +HUB_BASE_URL ?= http://localhost:8080 PKG = ./addon/... \ ./api/... \ @@ -116,3 +117,21 @@ ifeq (,$(wildcard $(INSTALL_TACKLE_SH))) } endif $(INSTALL_TACKLE_SH); + +# Run test targets always (not producing test dirs there). +.PHONY: test test-api test-integration + +# Run unit tests. +test: + go test -v ./auth/ + +# Run Hub REST API tests. +test-api: + HUB_BASE_URL=${HUB_BASE_URL} go test -v ./test/api/... + +# Run Hub API integration tests. +test-integration: + HUB_BASE_URL=${HUB_BASE_URL} go test -v ./test/integration/... + +# Run Hub test suite. +test-all: test-unit test-api test-integration diff --git a/binding/application.go b/binding/application.go new file mode 100644 index 000000000..b97d2c29c --- /dev/null +++ b/binding/application.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// Application API. +type Application struct { + // hub API client. + client *Client +} + +// +// Create an Application. +func (h *Application) Create(r *api.Application) (err error) { + err = h.client.Post(api.ApplicationsRoot, &r) + return +} + +// +// Get an Application by ID. +func (h *Application) Get(id uint) (r *api.Application, err error) { + r = &api.Application{} + path := Path(api.ApplicationRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List Applications. +func (h *Application) List() (list []api.Application, err error) { + list = []api.Application{} + err = h.client.Get(api.ApplicationsRoot, &list) + return +} + +// +// Update an Application. +func (h *Application) Update(r *api.Application) (err error) { + path := Path(api.ApplicationRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete an Application. +func (h *Application) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.ApplicationRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/businessservice.go b/binding/businessservice.go new file mode 100644 index 000000000..1448a8aa5 --- /dev/null +++ b/binding/businessservice.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// BusinessService API. +type BusinessService struct { + // hub API client. + client *Client +} + +// +// Create a BusinessService. +func (h *BusinessService) Create(r *api.BusinessService) (err error) { + err = h.client.Post(api.BusinessServicesRoot, &r) + return +} + +// +// Get a BusinessService by ID. +func (h *BusinessService) Get(id uint) (r *api.BusinessService, err error) { + r = &api.BusinessService{} + path := Path(api.BusinessServiceRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List BusinessServices. +func (h *BusinessService) List() (list []api.BusinessService, err error) { + list = []api.BusinessService{} + err = h.client.Get(api.BusinessServicesRoot, &list) + return +} + +// +// Update a BusinessService. +func (h *BusinessService) Update(r *api.BusinessService) (err error) { + path := Path(api.BusinessServiceRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a BusinessService. +func (h *BusinessService) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.BusinessServiceRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/client.go b/binding/client.go new file mode 100644 index 000000000..58277ee43 --- /dev/null +++ b/binding/client.go @@ -0,0 +1,723 @@ +package binding + +import ( + "archive/tar" + "bufio" + "bytes" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net" + "net/http" + "net/url" + "os" + pathlib "path" + "path/filepath" + "strings" + "time" + + "github.com/gin-gonic/gin/binding" + liberr "github.com/konveyor/controller/pkg/error" + "github.com/konveyor/tackle2-hub/api" +) + +const ( + RetryLimit = 60 + RetryDelay = time.Second * 10 +) + +// +// Param. +type Param struct { + Key string + Value string +} + +// +// Params mapping. +type Params map[string]interface{} + +// +// Path API path. +type Path string + +// +// Inject named parameters. +func (s Path) Inject(p Params) (out string) { + in := strings.Split(string(s), "/") + for i := range in { + if len(in[i]) < 1 { + continue + } + key := in[i][1:] + if v, found := p[key]; found { + in[i] = fmt.Sprintf("%v", v) + } + } + out = strings.Join(in, "/") + return +} + +// +// NewClient Constructs a new client +func NewClient(url, token string) (client *Client) { + client = &Client{ + baseURL: url, + token: token, + } + client.Retry = RetryLimit + return +} + +// +// Client provides a REST client. +type Client struct { + // baseURL for the nub. + baseURL string + // addon API token + token string + // transport + transport http.RoundTripper + // Retry limit. + Retry int + // Error + Error error +} + +// +// SetToken sets hub token on client +func (r *Client) SetToken(token string) { + r.token = token +} + +// +// Reset the client. +func (r *Client) Reset() { + r.Error = nil +} + +// +// Get a resource. +func (r *Client) Get(path string, object interface{}, params ...Param) (err error) { + request := func() (request *http.Request, err error) { + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodGet, + URL: r.join(path), + } + request.Header.Set(api.Accept, binding.MIMEJSON) + if len(params) > 0 { + q := request.URL.Query() + for _, p := range params { + q.Add(p.Key, p.Value) + } + request.URL.RawQuery = q.Encode() + } + return + } + reply, err := r.send(request) + if err != nil { + return + } + defer func() { + _ = reply.Body.Close() + }() + status := reply.StatusCode + switch status { + case http.StatusOK: + var body []byte + body, err = io.ReadAll(reply.Body) + if err != nil { + err = liberr.Wrap(err) + return + } + err = json.Unmarshal(body, object) + if err != nil { + err = liberr.Wrap(err) + return + } + case http.StatusNotFound: + err = &NotFound{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + + return +} + +// +// Post a resource. +func (r *Client) Post(path string, object interface{}) (err error) { + request := func() (request *http.Request, err error) { + bfr, err := json.Marshal(object) + if err != nil { + err = liberr.Wrap(err) + return + } + reader := bytes.NewReader(bfr) + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodPost, + Body: io.NopCloser(reader), + URL: r.join(path), + } + request.Header.Set(api.Accept, binding.MIMEJSON) + return + } + reply, err := r.send(request) + if err != nil { + return + } + status := reply.StatusCode + switch status { + case http.StatusOK, + http.StatusCreated: + var body []byte + body, err = io.ReadAll(reply.Body) + if err != nil { + err = liberr.Wrap(err) + return + } + err = json.Unmarshal(body, object) + if err != nil { + err = liberr.Wrap(err) + return + } + case http.StatusConflict: + err = &Conflict{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + + return +} + +// +// Put a resource. +func (r *Client) Put(path string, object interface{}, params ...Param) (err error) { + request := func() (request *http.Request, err error) { + bfr, err := json.Marshal(object) + if err != nil { + err = liberr.Wrap(err) + return + } + reader := bytes.NewReader(bfr) + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodPut, + Body: io.NopCloser(reader), + URL: r.join(path), + } + request.Header.Set(api.Accept, binding.MIMEJSON) + if len(params) > 0 { + q := request.URL.Query() + for _, p := range params { + q.Add(p.Key, p.Value) + } + request.URL.RawQuery = q.Encode() + } + return + } + reply, err := r.send(request) + if err != nil { + return + } + status := reply.StatusCode + switch status { + case http.StatusNoContent: + case http.StatusOK, + http.StatusCreated: + var body []byte + body, err = io.ReadAll(reply.Body) + if err != nil { + err = liberr.Wrap(err) + return + } + err = json.Unmarshal(body, object) + if err != nil { + err = liberr.Wrap(err) + return + } + case http.StatusNotFound: + err = &NotFound{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + + return +} + +// +// Delete a resource. +func (r *Client) Delete(path string, params ...Param) (err error) { + request := func() (request *http.Request, err error) { + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodDelete, + URL: r.join(path), + } + request.Header.Set(api.Accept, binding.MIMEJSON) + if len(params) > 0 { + q := request.URL.Query() + for _, p := range params { + q.Add(p.Key, p.Value) + } + request.URL.RawQuery = q.Encode() + } + return + } + reply, err := r.send(request) + if err != nil { + return + } + defer func() { + _ = reply.Body.Close() + }() + status := reply.StatusCode + switch status { + case http.StatusOK, + http.StatusNoContent: + case http.StatusNotFound: + err = &NotFound{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + + return +} + +// +// BucketGet downloads a file/directory. +// The source (path) is relative to the bucket root. +func (r *Client) BucketGet(source, destination string) (err error) { + request := func() (request *http.Request, err error) { + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodGet, + URL: r.join(source), + } + request.Header.Set(api.Accept, api.MIMEOCTETSTREAM) + return + } + reply, err := r.send(request) + if err != nil { + return + } + defer func() { + _ = reply.Body.Close() + }() + status := reply.StatusCode + switch status { + case http.StatusNoContent: + // Empty. + case http.StatusOK: + if reply.Header.Get(api.Directory) == api.DirectoryExpand { + err = r.getDir(reply.Body, destination) + } else { + err = r.getFile(reply.Body, source, destination) + } + case http.StatusNotFound: + err = &NotFound{Path: source} + default: + err = liberr.New(http.StatusText(status)) + } + return +} + +// +// BucketPut uploads a file/directory. +// The destination (path) is relative to the bucket root. +func (r *Client) BucketPut(source, destination string) (err error) { + isDir, err := r.isDir(source, true) + if err != nil { + return + } + request := func() (request *http.Request, err error) { + buf := new(bytes.Buffer) + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodPut, + Body: io.NopCloser(buf), + URL: r.join(destination), + } + request.Header.Set(api.Accept, api.MIMEOCTETSTREAM) + writer := multipart.NewWriter(buf) + defer func() { + _ = writer.Close() + }() + part, nErr := writer.CreateFormFile(api.FileField, pathlib.Base(source)) + if err != nil { + err = liberr.Wrap(nErr) + return + } + request.Header.Add( + api.ContentType, + writer.FormDataContentType()) + if isDir { + request.Header.Set(api.Directory, api.DirectoryExpand) + err = r.putDir(part, source) + } else { + err = r.putFile(part, source) + } + return + } + reply, err := r.send(request) + if err != nil { + return + } + status := reply.StatusCode + switch status { + case http.StatusOK, + http.StatusNoContent, + http.StatusCreated, + http.StatusAccepted: + case http.StatusNotFound: + err = &NotFound{Path: destination} + default: + err = liberr.New(http.StatusText(status)) + } + return +} + +// +// FileGet downloads a file. +func (r *Client) FileGet(path, destination string) (err error) { + request := func() (request *http.Request, err error) { + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodGet, + URL: r.join(path), + } + request.Header.Set(api.Accept, api.MIMEOCTETSTREAM) + return + } + reply, err := r.send(request) + if err != nil { + return + } + defer func() { + _ = reply.Body.Close() + }() + status := reply.StatusCode + switch status { + case http.StatusNoContent: + // Empty. + case http.StatusOK: + err = r.getFile(reply.Body, "", destination) + case http.StatusNotFound: + err = &NotFound{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + return +} + +// +// FilePut uploads a file. +// Returns the created File resource. +func (r *Client) FilePut(path, source string, object interface{}) (err error) { + isDir, err := r.isDir(source, true) + if err != nil { + return + } + if isDir { + err = liberr.New("Source cannot be directory.") + return + } + request := func() (request *http.Request, err error) { + buf := new(bytes.Buffer) + request = &http.Request{ + Header: http.Header{}, + Method: http.MethodPut, + Body: io.NopCloser(buf), + URL: r.join(path), + } + request.Header.Set(api.Accept, binding.MIMEJSON) + writer := multipart.NewWriter(buf) + defer func() { + _ = writer.Close() + }() + part, nErr := writer.CreateFormFile(api.FileField, pathlib.Base(source)) + if err != nil { + err = liberr.Wrap(nErr) + return + } + request.Header.Add( + api.ContentType, + writer.FormDataContentType()) + err = r.putFile(part, source) + return + } + reply, err := r.send(request) + if err != nil { + return + } + status := reply.StatusCode + switch status { + case http.StatusOK, + http.StatusCreated: + var body []byte + body, err = io.ReadAll(reply.Body) + if err != nil { + err = liberr.Wrap(err) + return + } + err = json.Unmarshal(body, object) + if err != nil { + err = liberr.Wrap(err) + return + } + case http.StatusConflict: + err = &Conflict{Path: path} + default: + err = liberr.New(http.StatusText(status)) + } + return +} + +// +// getDir downloads and expands a directory. +func (r *Client) getDir(body io.Reader, output string) (err error) { + zipReader, err := gzip.NewReader(body) + if err != nil { + err = liberr.Wrap(err) + return + } + defer func() { + _ = zipReader.Close() + }() + tarReader := tar.NewReader(zipReader) + for { + header, nErr := tarReader.Next() + if nErr != nil { + if nErr == io.EOF { + break + } else { + err = liberr.Wrap(nErr) + return + } + } + path := pathlib.Join(output, header.Name) + switch header.Typeflag { + case tar.TypeDir: + err = os.Mkdir(path, 0777) + if err != nil { + err = liberr.Wrap(err) + return + } + case tar.TypeReg: + file, nErr := os.Create(path) + if nErr != nil { + err = liberr.Wrap(nErr) + return + } + _, err = io.Copy(file, tarReader) + _ = file.Close() + default: + } + } + return +} + +// +// putDir archive and uploads a directory. +func (r *Client) putDir(writer io.Writer, input string) (err error) { + var tarOutput bytes.Buffer + tarWriter := tar.NewWriter(&tarOutput) + err = filepath.Walk( + input, + func(path string, entry os.FileInfo, wErr error) (err error) { + if wErr != nil { + err = liberr.Wrap(wErr) + return + } + if path == input { + return + } + header, nErr := tar.FileInfoHeader(entry, "") + if nErr != nil { + err = liberr.Wrap(nErr) + return + } + header.Name = path[len(input)+1:] + switch header.Typeflag { + case tar.TypeDir: + err = tarWriter.WriteHeader(header) + if err != nil { + err = liberr.Wrap(err) + return + } + case tar.TypeReg: + err = tarWriter.WriteHeader(header) + if err != nil { + err = liberr.Wrap(err) + return + } + file, nErr := os.Open(path) + if err != nil { + err = liberr.Wrap(nErr) + return + } + defer func() { + _ = file.Close() + }() + _, err = io.Copy(tarWriter, file) + if err != nil { + err = liberr.Wrap(err) + return + } + } + return + }) + if err != nil { + return + } + zipReader := bufio.NewReader(&tarOutput) + zipWriter := gzip.NewWriter(writer) + defer func() { + _ = zipWriter.Close() + }() + _, err = io.Copy(zipWriter, zipReader) + return +} + +// +// getFile downloads plain file. +func (r *Client) getFile(body io.Reader, path, output string) (err error) { + isDir, err := r.isDir(output, false) + if err != nil { + return + } + if isDir { + output = pathlib.Join( + output, + pathlib.Base(path)) + } + file, err := os.Create(output) + if err != nil { + err = liberr.Wrap(err) + return + } + defer func() { + _ = file.Close() + }() + _, err = io.Copy(file, body) + return +} + +// +// putFile uploads plain file. +func (r *Client) putFile(writer io.Writer, input string) (err error) { + file, err := os.Open(input) + if err != nil { + err = liberr.Wrap(err) + return + } + defer func() { + _ = file.Close() + }() + _, err = io.Copy(writer, file) + return +} + +// +// isDir determines if the path is a directory. +// The `must` specifies if the path must exist. +func (r *Client) isDir(path string, must bool) (b bool, err error) { + st, err := os.Stat(path) + if err == nil { + b = st.IsDir() + return + } + if os.IsNotExist(err) { + if must { + err = liberr.Wrap(err) + } else { + err = nil + } + } else { + err = liberr.Wrap(err) + } + return +} + +// +// Send the request. +// Resilient against transient hub availability. +func (r *Client) send(rb func() (*http.Request, error)) (response *http.Response, err error) { + var request *http.Request + if r.Error != nil { + err = r.Error + return + } + err = r.buildTransport() + if err != nil { + return + } + for i := 0; ; i++ { + request, err = rb() + if err != nil { + return + } + request.Header.Set(api.Authorization, r.token) + client := http.Client{Transport: r.transport} + response, err = client.Do(request) + if err != nil { + netErr := &net.OpError{} + if errors.As(err, &netErr) { + if i < r.Retry { + Log.Info(err.Error()) + time.Sleep(RetryDelay) + continue + } else { + r.Error = liberr.Wrap(err) + err = r.Error + return + } + } else { + err = liberr.Wrap(err) + return + } + } else { + Log.Info( + fmt.Sprintf( + "|%d| %s %s", + response.StatusCode, + request.Method, + request.URL.Path)) + break + } + } + return +} + +// +// buildTransport builds transport. +func (r *Client) buildTransport() (err error) { + if r.transport != nil { + return + } + r.transport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 10 * time.Second, + }).DialContext, + MaxIdleConns: 3, + IdleConnTimeout: 10 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + return +} + +// +// Join the URL. +func (r *Client) join(path string) (parsedURL *url.URL) { + parsedURL, _ = url.Parse(r.baseURL) + parsedURL.Path = pathlib.Join(parsedURL.Path, path) + return +} diff --git a/binding/error.go b/binding/error.go new file mode 100644 index 000000000..0a097431a --- /dev/null +++ b/binding/error.go @@ -0,0 +1,56 @@ +package binding + +import ( + "fmt" +) + +// +// SoftError A "soft" anticipated error. +type SoftError struct { + Reason string +} + +func (e *SoftError) Error() (s string) { + return e.Reason +} + +func (e *SoftError) Is(err error) (matched bool) { + _, matched = err.(*SoftError) + return +} + +func (e *SoftError) Soft() *SoftError { + return e +} + +// +// Conflict reports 409 error. +type Conflict struct { + SoftError + Path string +} + +func (e Conflict) Error() string { + return fmt.Sprintf("POST: path:%s (conflict)", e.Path) +} + +func (e *Conflict) Is(err error) (matched bool) { + _, matched = err.(*Conflict) + return +} + +// +// NotFound reports 404 error. +type NotFound struct { + SoftError + Path string +} + +func (e NotFound) Error() string { + return fmt.Sprintf("HTTP path:%s (not-found)", e.Path) +} + +func (e *NotFound) Is(err error) (matched bool) { + _, matched = err.(*NotFound) + return +} diff --git a/binding/jobfunction.go b/binding/jobfunction.go new file mode 100644 index 000000000..69655e722 --- /dev/null +++ b/binding/jobfunction.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// JobFunction API. +type JobFunction struct { + // hub API client. + client *Client +} + +// +// Create a JobFunction. +func (h *JobFunction) Create(r *api.JobFunction) (err error) { + err = h.client.Post(api.JobFunctionsRoot, &r) + return +} + +// +// Get a JobFunction by ID. +func (h *JobFunction) Get(id uint) (r *api.JobFunction, err error) { + r = &api.JobFunction{} + path := Path(api.JobFunctionRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List JobFunctions. +func (h *JobFunction) List() (list []api.JobFunction, err error) { + list = []api.JobFunction{} + err = h.client.Get(api.JobFunctionsRoot, &list) + return +} + +// +// Update a JobFunction. +func (h *JobFunction) Update(r *api.JobFunction) (err error) { + path := Path(api.JobFunctionRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a JobFunction. +func (h *JobFunction) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.JobFunctionRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/richclient.go b/binding/richclient.go new file mode 100644 index 000000000..45ca0cce1 --- /dev/null +++ b/binding/richclient.go @@ -0,0 +1,95 @@ +package binding + +import ( + "github.com/konveyor/controller/pkg/logging" + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/settings" +) + +var ( + Settings = &settings.Settings + Log = logging.WithName("binding") +) + +func init() { + err := Settings.Load() + if err != nil { + panic(err) + } +} + +// The RichClient provides API integration. +type RichClient struct { + // Resources APIs. + Application Application + BusinessService BusinessService + JobFunction JobFunction + Stakeholder Stakeholder + StakeholderGroup StakeholderGroup + Tag Tag + TagCategory TagCategory + Task Task + + // A REST client. + client *Client +} + +// Client provides the REST client. +func (h *RichClient) Client() *Client { + return h.client +} + +// newRichClient builds a new RichClient object. +func New(baseUrl string) (r *RichClient) { + // + // Build REST client. + client := NewClient(baseUrl, "") + + // + // Build RichClient. + r = &RichClient{ + Application: Application{ + client: client, + }, + BusinessService: BusinessService{ + client: client, + }, + JobFunction: JobFunction{ + client: client, + }, + Stakeholder: Stakeholder{ + client: client, + }, + StakeholderGroup: StakeholderGroup{ + client: client, + }, + Tag: Tag{ + client: client, + }, + TagCategory: TagCategory{ + client: client, + }, + Task: Task{ + client: client, + }, + client: client, + } + + Log.Info("Hub RichClient created.") + + return +} + +func (r *RichClient) Login(user, password string) (err error) { + // + // Build REST client. + login := api.Login{User: user, Password: password} + + // Login. + err = r.client.Post(api.AuthLoginRoot, &login) + if err != nil { + return + } + r.client.SetToken(login.Token) + return +} diff --git a/binding/stakeholder.go b/binding/stakeholder.go new file mode 100644 index 000000000..12a59326e --- /dev/null +++ b/binding/stakeholder.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// Stakeholder API. +type Stakeholder struct { + // hub API client. + client *Client +} + +// +// Create a Stakeholder. +func (h *Stakeholder) Create(r *api.Stakeholder) (err error) { + err = h.client.Post(api.StakeholdersRoot, &r) + return +} + +// +// Get a Stakeholder by ID. +func (h *Stakeholder) Get(id uint) (r *api.Stakeholder, err error) { + r = &api.Stakeholder{} + path := Path(api.StakeholderRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List Stakeholders. +func (h *Stakeholder) List() (list []api.Stakeholder, err error) { + list = []api.Stakeholder{} + err = h.client.Get(api.StakeholdersRoot, &list) + return +} + +// +// Update a Stakeholder. +func (h *Stakeholder) Update(r *api.Stakeholder) (err error) { + path := Path(api.StakeholderRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a Stakeholder. +func (h *Stakeholder) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.StakeholderRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/stakeholdergroup.go b/binding/stakeholdergroup.go new file mode 100644 index 000000000..bb4362b6a --- /dev/null +++ b/binding/stakeholdergroup.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// StakeholderGroup API. +type StakeholderGroup struct { + // hub API client. + client *Client +} + +// +// Create a StakeholderGroup. +func (h *StakeholderGroup) Create(r *api.StakeholderGroup) (err error) { + err = h.client.Post(api.StakeholderGroupsRoot, &r) + return +} + +// +// Get a StakeholderGroup by ID. +func (h *StakeholderGroup) Get(id uint) (r *api.StakeholderGroup, err error) { + r = &api.StakeholderGroup{} + path := Path(api.StakeholderGroupRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List StakeholderGroups. +func (h *StakeholderGroup) List() (list []api.StakeholderGroup, err error) { + list = []api.StakeholderGroup{} + err = h.client.Get(api.StakeholderGroupsRoot, &list) + return +} + +// +// Update a StakeholderGroup. +func (h *StakeholderGroup) Update(r *api.StakeholderGroup) (err error) { + path := Path(api.StakeholderGroupRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a StakeholderGroup. +func (h *StakeholderGroup) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.StakeholderGroupRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/tag.go b/binding/tag.go new file mode 100644 index 000000000..640dc10b8 --- /dev/null +++ b/binding/tag.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// Tag API. +type Tag struct { + // hub API client. + client *Client +} + +// +// Create a Tag. +func (h *Tag) Create(r *api.Tag) (err error) { + err = h.client.Post(api.TagsRoot, &r) + return +} + +// +// Get a Tag by ID. +func (h *Tag) Get(id uint) (r *api.Tag, err error) { + r = &api.Tag{} + path := Path(api.TagRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List Tags. +func (h *Tag) List() (list []api.Tag, err error) { + list = []api.Tag{} + err = h.client.Get(api.TagsRoot, &list) + return +} + +// +// Update a Tag. +func (h *Tag) Update(r *api.Tag) (err error) { + path := Path(api.TagRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a Tag. +func (h *Tag) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.TagRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/tagcategory.go b/binding/tagcategory.go new file mode 100644 index 000000000..1445821dc --- /dev/null +++ b/binding/tagcategory.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// TagCategory API. +type TagCategory struct { + // hub API client. + client *Client +} + +// +// Create a TagCategory. +func (h *TagCategory) Create(r *api.TagCategory) (err error) { + err = h.client.Post(api.TagCategoriesRoot, &r) + return +} + +// +// Get a TagCategory by ID. +func (h *TagCategory) Get(id uint) (r *api.TagCategory, err error) { + r = &api.TagCategory{} + path := Path(api.TagCategoryRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List TagCategories. +func (h *TagCategory) List() (list []api.TagCategory, err error) { + list = []api.TagCategory{} + err = h.client.Get(api.TagCategoriesRoot, &list) + return +} + +// +// Update a TagCategory. +func (h *TagCategory) Update(r *api.TagCategory) (err error) { + path := Path(api.TagCategoryRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a TagCategory. +func (h *TagCategory) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.TagCategoryRoot).Inject(Params{api.ID: id})) + return +} diff --git a/binding/task.go b/binding/task.go new file mode 100644 index 000000000..a41fba4e7 --- /dev/null +++ b/binding/task.go @@ -0,0 +1,51 @@ +package binding + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// +// Task API. +type Task struct { + // hub API client. + client *Client +} + +// +// Create a Task. +func (h *Task) Create(r *api.Task) (err error) { + err = h.client.Post(api.TasksRoot, &r) + return +} + +// +// Get a Task by ID. +func (h *Task) Get(id uint) (r *api.Task, err error) { + r = &api.Task{} + path := Path(api.TaskRoot).Inject(Params{api.ID: id}) + err = h.client.Get(path, r) + return +} + +// +// List Tasks. +func (h *Task) List() (list []api.Task, err error) { + list = []api.Task{} + err = h.client.Get(api.TasksRoot, &list) + return +} + +// +// Update a Task. +func (h *Task) Update(r *api.Task) (err error) { + path := Path(api.TaskRoot).Inject(Params{api.ID: r.ID}) + err = h.client.Put(path, r) + return +} + +// +// Delete a Task. +func (h *Task) Delete(id uint) (err error) { + err = h.client.Delete(Path(api.TaskRoot).Inject(Params{api.ID: id})) + return +} diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..6cfee559f --- /dev/null +++ b/test/README.md @@ -0,0 +1,69 @@ +# Hub Tests + +Hub tests consist of following parts: +- Unit tests ```$ make test``` +- REST API tests ```$ make test-api``` +- Integration tests ```$ make test-integration``` +- WIP Export/import tests + +All tests can be executed with ```$ make test-all``` which will run all available tests. + +## General information + +- Tests are written in golang to fit well to the Konveyor project components. +- Each test is responsible for setup its test data and clean it when finished. +- The main way of interacting with Hub is its API, to make testing easier, following tools are provided: + - Test ```client``` package wrapping addon.Client provides API methods like Get/Post/etc., Should/Must and other equality assertions. + - Hub's ```API``` package provides predefined routes and resources struct definition. + - Each resource has a test package that tests the resource endpoint methods and provides fixtures and basic CRUD methods (like ```application.Create(&testApp)```) to other tests. + + +## REST API + +API tests can be executed on locally running Hub in development mode (without other Konveyor components using DISCONNECTED=1). + +``` +$ export DISCONNECTED=1 +$ make run +``` + +``` +$ export HUB_BASE_URL=http://localhost:8080 +$ go test -v ./test/api/ +``` + +Or tests can run against running Hub installation (example with minikube below). + +``` +$ export HUB_BASE_URL="http://$(minikube ip)/hub" +$ export KEYCLOAK_ADMIN_USER="admin" +$ export KEYCLOAK_ADMIN_PASS="" +$ go test -v ./test/api/ +``` + +Sample output +``` +$ make test-api +echo "Using Hub API from http://192.168.39.236/hub" +Using Hub API from http://192.168.39.236/hub +go test -v ./test/api/... +{"level":"info","ts":1679056454.0379033,"logger":"addon","msg":"Addon (adapter) created."} +{"level":"info","ts":1679056454.041117,"logger":"addon","msg":"|201| POST /hub/auth/login"} +... +=== RUN TestApplicationUpdateName +=== RUN TestApplicationUpdateName/Update_application_Pathfinder +{"level":"info","ts":1679056454.1980724,"logger":"addon","msg":"|201| POST /hub/applications"} +{"level":"info","ts":1679056454.2010725,"logger":"addon","msg":"|204| PUT /hub/applications/1"} +{"level":"info","ts":1679056454.2033865,"logger":"addon","msg":"|200| GET /hub/applications/1"} +{"level":"info","ts":1679056454.2071495,"logger":"addon","msg":"|204| DELETE /hub/applications/1"} +=== RUN TestApplicationUpdateName/Update_application_Minimal_application +{"level":"info","ts":1679056454.2095335,"logger":"addon","msg":"|201| POST /hub/applications"} +{"level":"info","ts":1679056454.2123275,"logger":"addon","msg":"|204| PUT /hub/applications/1"} +{"level":"info","ts":1679056454.2146227,"logger":"addon","msg":"|200| GET /hub/applications/1"} +{"level":"info","ts":1679056454.2210302,"logger":"addon","msg":"|204| DELETE /hub/applications/1"} +--- PASS: TestApplicationUpdateName (0.03s) + --- PASS: TestApplicationUpdateName/Update_application_Pathfinder (0.01s) + --- PASS: TestApplicationUpdateName/Update_application_Minimal_application (0.01s) +PASS +ok github.com/konveyor/tackle2-hub/test/api/application 0.194s +``` diff --git a/test/api/README.md b/test/api/README.md new file mode 100644 index 000000000..0436924c8 --- /dev/null +++ b/test/api/README.md @@ -0,0 +1,5 @@ +# API Tests + +(Test) Client package and set of test packages for each Hub resource providing fixture and CRUD method for other tests. + +Tests should work only with Hub REST API, no Hub backgroud processes or other components (like Pathfinder) are present. Test execution time should not be more than few seconds. diff --git a/test/api/application/bucket_test.go b/test/api/application/bucket_test.go new file mode 100644 index 000000000..1a1739fbb --- /dev/null +++ b/test/api/application/bucket_test.go @@ -0,0 +1,26 @@ +package application + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestApplicationBucket(t *testing.T) { + // Test Facts subresource on the first sample application only. + application := Minimal + + // Create the application. + assert.Must(t, Application.Create(&application)) + + // Get the bucket to check if it was created. + err := Client.BucketGet(binding.Path(api.BucketRoot).Inject(binding.Params{api.ID: application.Bucket.ID}), "/dev/null") + if err != nil { + t.Errorf(err.Error()) + } + + // Clean the application. + assert.Must(t, Application.Delete(application.ID)) +} diff --git a/test/api/application/create_get_delete_test.go b/test/api/application/create_get_delete_test.go new file mode 100644 index 000000000..adb3e91af --- /dev/null +++ b/test/api/application/create_get_delete_test.go @@ -0,0 +1,91 @@ +package application + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestApplicationCreateGetDelete(t *testing.T) { + // Create on array of Applications calls subtest + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + assert.Should(t, Application.Create(&r)) + + // Try get. + got, err := Application.Get(r.ID) + assert.Should(t, err) + + // Assert the get response. + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Try list. + gotList, err := Application.List() + assert.Should(t, err) + + // Assert the list response. + foundR := api.Application{} + for _, listR := range gotList { + if listR.Name == r.Name && listR.ID == r.ID { + foundR = listR + break + } + } + if assert.FlatEqual(foundR, r) { + t.Errorf("Different list entry error. Got %v, expected %v", foundR, r) + } + + // Try delete. + assert.Should(t, Application.Delete(got.ID)) + + // Check the created application was deleted. + _, err = Application.Get(r.ID) + if err == nil { + t.Fatalf("Exits, but should be deleted: %v", r) + } + }) + } +} + +func TestApplicationNotCreateDuplicates(t *testing.T) { + r := Minimal + + // Create sample. + assert.Should(t, Application.Create(&r)) + + // Prepare Application with duplicate Name. + dup := &api.Application{ + Name: r.Name, + } + + // Try create the duplicate. + err := Application.Create(dup) + if err == nil { + t.Errorf("Created duplicate application: %v", dup) + + // Clean the duplicate. + assert.Must(t, Application.Delete(dup.ID)) + } + + // Clean. + assert.Must(t, Application.Delete(r.ID)) +} + +func TestApplicationNotCreateWithoutName(t *testing.T) { + // Prepare Application without Name. + r := &api.Application{ + Name: "", + } + + // Try create the duplicate Application. + err := Application.Create(r) + if err == nil { + t.Errorf("Created empty application: %v", r) + + // Clean. + assert.Must(t, Application.Delete(r.ID)) + } +} diff --git a/test/api/application/facts_test.go b/test/api/application/facts_test.go new file mode 100644 index 000000000..cb29f1dec --- /dev/null +++ b/test/api/application/facts_test.go @@ -0,0 +1,124 @@ +package application + +import ( + "fmt" + "testing" + + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/assert" +) + +var SampleFacts = []*api.Fact{ + { + Key: "pet", + Value: "{\"kind\":\"dog\",\"Age\":4}", + Source: "test", + }, + { + Key: "address", + Value: "{\"street\":\"Maple\",\"State\":\"AL\"}", + Source: "test", + }, +} + +func TestApplicationFactCRUD(t *testing.T) { + // Test Facts subresource on the first sample application only. + application := Minimal + + // Create the application. + assert.Must(t, Application.Create(&application)) + + // Test Facts subresource. + for _, r := range SampleFacts { + t.Run(fmt.Sprintf("Fact %s application %s", r.Key, application.Name), func(t *testing.T) { + factPath := binding.Path(api.ApplicationFactRoot).Inject(binding.Params{api.ID: application.ID, api.Key: r.Key, api.Source: r.Source}) + + // Create. + err := Client.Post(binding.Path(api.ApplicationFactsRoot).Inject(binding.Params{api.ID: application.ID}), &r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got := api.Fact{} + err = Client.Get(factPath, &got) + if err != nil { + t.Errorf(err.Error()) + } + // Not sure about map[] wrapping of the Fact Value (interface) by the API + //if !reflect.DeepEqual(got.Value, fact.Value) { + // t.Errorf("Different fact value error. Got %v, expected %v", got.Value, fact.Value) + //} + + // Update. + updated := api.Fact{ + Value: fmt.Sprintf("{\"%s\":\"%s\"}", r.Key, "updated"), + } + err = Client.Put(factPath, updated) + if err != nil { + t.Errorf(err.Error()) + } + + // Get the updated. + got = api.Fact{} + err = Client.Get(factPath, &got) + if err != nil { + t.Errorf(err.Error()) + } + //if !reflect.DeepEqual(got.Value, updated.Value) { + // t.Errorf("Different updated fact value error. Got %v, expected %v", got.Value, updated.Value) + //} + + // Delete. + err = Client.Delete(factPath) + if err != nil { + t.Errorf(err.Error()) + } + + // Check the it was deleted. + err = Client.Get(factPath, &got) + if err == nil { + t.Errorf("Exits, but should be deleted: %v", r) + } + }) + } + + // Clean the application. + assert.Must(t, Application.Delete(application.ID)) +} + +func TestApplicationFactsList(t *testing.T) { + // Test Facts subresource on the first sample application only. + application := Minimal + + // Create the application. + assert.Must(t, Application.Create(&application)) + + // Create facts. + for _, r := range SampleFacts { + err := Client.Post(binding.Path(api.ApplicationFactsRoot).Inject(binding.Params{api.ID: application.ID}), &r) + if err != nil { + t.Errorf(err.Error()) + } + } + + // Check facts list with and without trailing slash (client maybe removes it anyway). + factsPathSuffix := []string{"facts", "facts/"} + for _, pathSuffix := range factsPathSuffix { + t.Run(fmt.Sprintf("Fact list application %s with %s", application.Name, pathSuffix), func(t *testing.T) { + got := []api.Fact{} + err := Client.Get(fmt.Sprintf("%s/%s", binding.Path(api.ApplicationRoot).Inject(binding.Params{api.ID: application.ID}), pathSuffix), &got) + if err != nil { + t.Errorf("Get list error: %v", err.Error()) + } + if len(got) != len(SampleFacts) { + t.Errorf("Different length of fact list error. Got %d, expected %d", len(got), len(SampleFacts)) + } + // Compare returned list values? + }) + } + + // Clean the application. + assert.Must(t, Application.Delete(application.ID)) +} diff --git a/test/api/application/pkg.go b/test/api/application/pkg.go new file mode 100644 index 000000000..86107b4fe --- /dev/null +++ b/test/api/application/pkg.go @@ -0,0 +1,24 @@ +package application + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + Client *binding.Client + RichClient *binding.RichClient + Application binding.Application +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Access REST client directly (some test API call need it) + Client = RichClient.Client() + + // Shortcut for Application-related RichClient methods. + Application = RichClient.Application +} diff --git a/test/api/application/samples.go b/test/api/application/samples.go new file mode 100644 index 000000000..0faa42260 --- /dev/null +++ b/test/api/application/samples.go @@ -0,0 +1,24 @@ +package application + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid Application resources for tests and reuse. +// Important: initialize test application from this samples, not use it directly to not affect other tests. +var ( + Minimal = api.Application{ + Name: "Minimal application", + } + PathfinderGit = api.Application{ + Name: "Pathfinder", + Description: "Tackle Pathfinder application.", + Repository: &api.Repository{ + Kind: "git", + URL: "https://github.com/konveyor/tackle-pathfinder.git", + Branch: "1.2.0", + }, + } + Samples = []api.Application{Minimal, PathfinderGit} +) + diff --git a/test/api/application/update_test.go b/test/api/application/update_test.go new file mode 100644 index 000000000..1b1da85c2 --- /dev/null +++ b/test/api/application/update_test.go @@ -0,0 +1,35 @@ +package application + +import ( + "fmt" + "reflect" + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestApplicationUpdateName(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + assert.Must(t, Application.Create(&r)) + + // Update. + update := r + update.Name = fmt.Sprint(r.Name, " updated") + assert.Should(t, Application.Update(&update)) + + // Check the updated. + got, err := Application.Get(r.ID) + assert.Should(t, err) + if !reflect.DeepEqual(got.Name, update.Name) { + t.Errorf("Different updated name error. Got %v, expected %v", got.Name, update.Name) + } + + // Clean. + assert.Must(t, Application.Delete(r.ID)) + }) + } +} + +// Tests updating different Applications attributes and references resources will be added here. diff --git a/test/api/businesservice/api_test.go b/test/api/businesservice/api_test.go new file mode 100644 index 000000000..b6f68cab8 --- /dev/null +++ b/test/api/businesservice/api_test.go @@ -0,0 +1,76 @@ +package businessservice + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestBusinessServiceCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := BusinessService.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := BusinessService.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = BusinessService.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = BusinessService.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = BusinessService.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = BusinessService.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestBusinessServiceList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, BusinessService.Create(&sample)) + samples[name] = sample + } + + got, err := BusinessService.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, BusinessService.Delete(r.ID)) + } +} diff --git a/test/api/businesservice/pkg.go b/test/api/businesservice/pkg.go new file mode 100644 index 000000000..57e8d16ee --- /dev/null +++ b/test/api/businesservice/pkg.go @@ -0,0 +1,20 @@ +package businessservice + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + BusinessService binding.BusinessService +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for BusinessService-related RichClient methods. + BusinessService = RichClient.BusinessService +} diff --git a/test/api/businesservice/samples.go b/test/api/businesservice/samples.go new file mode 100644 index 000000000..4118c900d --- /dev/null +++ b/test/api/businesservice/samples.go @@ -0,0 +1,16 @@ +package businessservice + +import "github.com/konveyor/tackle2-hub/api" + +// Set of valid resources for tests and reuse. +var ( + Marketing = api.BusinessService{ + Name: "Marketing", + Description: "Marketing dept service.", + } + Sales = api.BusinessService{ + Name: "Sales", + Description: "Sales support service.", + } + Samples = []api.BusinessService{Marketing, Sales} +) \ No newline at end of file diff --git a/test/api/client/client.go b/test/api/client/client.go new file mode 100644 index 000000000..015c57b71 --- /dev/null +++ b/test/api/client/client.go @@ -0,0 +1,23 @@ +package client + +import ( + "fmt" + + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/settings" +) + + +func PrepareRichClient() (richClient *binding.RichClient) { + // Prepare RichClient and login to Hub API + richClient = binding.New(settings.Settings.Addon.Hub.URL) + err := richClient.Login(settings.Settings.Auth.Keycloak.Admin.User, settings.Settings.Auth.Keycloak.Admin.Pass) + if err != nil { + panic(fmt.Sprintf("Cannot login to API: %v.", err.Error())) + } + + // Disable HTTP requests retry for network-related errors to fail quickly. + richClient.Client().Retry = 0 + + return +} \ No newline at end of file diff --git a/test/api/jobfunction/api_test.go b/test/api/jobfunction/api_test.go new file mode 100644 index 000000000..6e27c265d --- /dev/null +++ b/test/api/jobfunction/api_test.go @@ -0,0 +1,87 @@ +package jobfunction + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestJobFunctionCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := JobFunction.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := JobFunction.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = JobFunction.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = JobFunction.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = JobFunction.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = JobFunction.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestJobFunctionList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, JobFunction.Create(&sample)) + samples[name] = sample + } + + got, err := JobFunction.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, JobFunction.Delete(r.ID)) + } +} + + +func TestJobFunctionSeed(t *testing.T) { + got, err := JobFunction.List() + if err != nil { + t.Errorf(err.Error()) + } + if len(got) < 1 { + t.Errorf("Seed looks empty, but it shouldn't.") + } +} diff --git a/test/api/jobfunction/pkg.go b/test/api/jobfunction/pkg.go new file mode 100644 index 000000000..9514bd97a --- /dev/null +++ b/test/api/jobfunction/pkg.go @@ -0,0 +1,20 @@ +package jobfunction + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + JobFunction binding.JobFunction +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for JobFunction-related RichClient methods. + JobFunction = RichClient.JobFunction +} diff --git a/test/api/jobfunction/samples.go b/test/api/jobfunction/samples.go new file mode 100644 index 000000000..8805944d1 --- /dev/null +++ b/test/api/jobfunction/samples.go @@ -0,0 +1,16 @@ +package jobfunction + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid resources for tests and reuse. +var ( + Engineer = api.JobFunction{ + Name: "Engineer", + } + Manager = api.JobFunction{ + Name: "Manager", + } + Samples = []api.JobFunction{Engineer, Manager} +) diff --git a/test/api/stakeholder/api_test.go b/test/api/stakeholder/api_test.go new file mode 100644 index 000000000..dac5dd622 --- /dev/null +++ b/test/api/stakeholder/api_test.go @@ -0,0 +1,76 @@ +package stakeholder + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestStakeholderCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := Stakeholder.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := Stakeholder.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = Stakeholder.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = Stakeholder.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = Stakeholder.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = Stakeholder.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestStakeholderList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, Stakeholder.Create(&sample)) + samples[name] = sample + } + + got, err := Stakeholder.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, Stakeholder.Delete(r.ID)) + } +} diff --git a/test/api/stakeholder/pkg.go b/test/api/stakeholder/pkg.go new file mode 100644 index 000000000..af5451a69 --- /dev/null +++ b/test/api/stakeholder/pkg.go @@ -0,0 +1,20 @@ +package stakeholder + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + Stakeholder binding.Stakeholder +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for Stakeholder-related RichClient methods. + Stakeholder = RichClient.Stakeholder +} diff --git a/test/api/stakeholder/samples.go b/test/api/stakeholder/samples.go new file mode 100644 index 000000000..57f9d0c6f --- /dev/null +++ b/test/api/stakeholder/samples.go @@ -0,0 +1,18 @@ +package stakeholder + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid resources for tests and reuse. +var ( + Alice = api.Stakeholder{ + Name: "Alice", + Email: "alice@acme.local", + } + Bob = api.Stakeholder{ + Name: "Bob", + Email: "bob@acme-supplier.local", + } + Samples = []api.Stakeholder{Alice, Bob} +) diff --git a/test/api/stakeholdergroup/api_test.go b/test/api/stakeholdergroup/api_test.go new file mode 100644 index 000000000..1827fa0b0 --- /dev/null +++ b/test/api/stakeholdergroup/api_test.go @@ -0,0 +1,76 @@ +package stakeholdergroup + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestStakeholderGroupCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := StakeholderGroup.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := StakeholderGroup.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = StakeholderGroup.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = StakeholderGroup.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = StakeholderGroup.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = StakeholderGroup.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestStakeholderGroupList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, StakeholderGroup.Create(&sample)) + samples[name] = sample + } + + got, err := StakeholderGroup.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, StakeholderGroup.Delete(r.ID)) + } +} diff --git a/test/api/stakeholdergroup/pkg.go b/test/api/stakeholdergroup/pkg.go new file mode 100644 index 000000000..b6c68f3d3 --- /dev/null +++ b/test/api/stakeholdergroup/pkg.go @@ -0,0 +1,20 @@ +package stakeholdergroup + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + StakeholderGroup binding.StakeholderGroup +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for StakeholderGroup-related RichClient methods. + StakeholderGroup = RichClient.StakeholderGroup +} diff --git a/test/api/stakeholdergroup/samples.go b/test/api/stakeholdergroup/samples.go new file mode 100644 index 000000000..60cfc489f --- /dev/null +++ b/test/api/stakeholdergroup/samples.go @@ -0,0 +1,18 @@ +package stakeholdergroup + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid resources for tests and reuse. +var ( + Mgmt = api.StakeholderGroup{ + Name: "Mgmt", + Description: "Management stakeholder group.", + } + Engineering = api.StakeholderGroup{ + Name: "Engineering", + Description: "Engineering team.", + } + Samples = []api.StakeholderGroup{Mgmt, Engineering} +) diff --git a/test/api/tag/api_test.go b/test/api/tag/api_test.go new file mode 100644 index 000000000..5e28b1de7 --- /dev/null +++ b/test/api/tag/api_test.go @@ -0,0 +1,87 @@ +package tag + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestTagCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := Tag.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := Tag.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = Tag.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = Tag.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = Tag.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = Tag.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestTagList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, Tag.Create(&sample)) + samples[name] = sample + } + + got, err := Tag.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, Tag.Delete(r.ID)) + } +} + + +func TestTagSeed(t *testing.T) { + got, err := Tag.List() + if err != nil { + t.Errorf(err.Error()) + } + if len(got) < 1 { + t.Errorf("Seed looks empty, but it shouldn't.") + } +} \ No newline at end of file diff --git a/test/api/tag/pkg.go b/test/api/tag/pkg.go new file mode 100644 index 000000000..4233d6337 --- /dev/null +++ b/test/api/tag/pkg.go @@ -0,0 +1,20 @@ +package tag + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + Tag binding.Tag +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for Tag-related RichClient methods. + Tag = RichClient.Tag +} diff --git a/test/api/tag/samples.go b/test/api/tag/samples.go new file mode 100644 index 000000000..36792efa1 --- /dev/null +++ b/test/api/tag/samples.go @@ -0,0 +1,22 @@ +package tag + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid resources for tests and reuse. +var ( + TestLinux = api.Tag{ + Name: "Test Linux", + Category: api.Ref{ + ID: 1, // Category from seeds. + }, + } + TestRHEL = api.Tag{ + Name: "Test RHEL", + Category: api.Ref{ + ID: 2, // Category from seeds. + }, + } + Samples = []api.Tag{TestLinux, TestRHEL} +) diff --git a/test/api/tagcategory/api_test.go b/test/api/tagcategory/api_test.go new file mode 100644 index 000000000..fa0ca44e9 --- /dev/null +++ b/test/api/tagcategory/api_test.go @@ -0,0 +1,87 @@ +package tagcategory + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestTagCategoryCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := TagCategory.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := TagCategory.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = TagCategory.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = TagCategory.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = TagCategory.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = TagCategory.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestTagCategoryList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, TagCategory.Create(&sample)) + samples[name] = sample + } + + got, err := TagCategory.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, TagCategory.Delete(r.ID)) + } +} + + +func TestTagCategorySeed(t *testing.T) { + got, err := TagCategory.List() + if err != nil { + t.Errorf(err.Error()) + } + if len(got) < 1 { + t.Errorf("Seed looks empty, but it shouldn't.") + } +} diff --git a/test/api/tagcategory/pkg.go b/test/api/tagcategory/pkg.go new file mode 100644 index 000000000..0e51b55a0 --- /dev/null +++ b/test/api/tagcategory/pkg.go @@ -0,0 +1,20 @@ +package tagcategory + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + TagCategory binding.TagCategory +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for TagCategory-related RichClient methods. + TagCategory = RichClient.TagCategory +} diff --git a/test/api/tagcategory/samples.go b/test/api/tagcategory/samples.go new file mode 100644 index 000000000..5c0ce29d7 --- /dev/null +++ b/test/api/tagcategory/samples.go @@ -0,0 +1,18 @@ +package tagcategory + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid TagCategories resources for tests and reuse. +var ( + TestOS = api.TagCategory{ + Name: "Test OS", + Color: "#dd0000", + } + TestLanguage = api.TagCategory{ + Name: "Test Language", + Color: "#0000dd", + } + Samples = []api.TagCategory{TestOS, TestLanguage} +) diff --git a/test/api/task/api_test.go b/test/api/task/api_test.go new file mode 100644 index 000000000..526d550ad --- /dev/null +++ b/test/api/task/api_test.go @@ -0,0 +1,76 @@ +package task + +import ( + "testing" + + "github.com/konveyor/tackle2-hub/test/assert" +) + +func TestTaskCRUD(t *testing.T) { + for _, r := range Samples { + t.Run(r.Name, func(t *testing.T) { + // Create. + err := Task.Create(&r) + if err != nil { + t.Errorf(err.Error()) + } + + // Get. + got, err := Task.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, r) { + t.Errorf("Different response error. Got %v, expected %v", got, r) + } + + // Update. + r.Name = "Updated " + r.Name + err = Task.Update(&r) + if err != nil { + t.Errorf(err.Error()) + } + + got, err = Task.Get(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + if got.Name != r.Name { + t.Errorf("Different response error. Got %s, expected %s", got.Name, r.Name) + } + + // Delete. + err = Task.Delete(r.ID) + if err != nil { + t.Errorf(err.Error()) + } + + _, err = Task.Get(r.ID) + if err == nil { + t.Errorf("Resource exits, but should be deleted: %v", r) + } + }) + } +} + +func TestTaskList(t *testing.T) { + samples := Samples + + for name := range samples { + sample := samples[name] + assert.Must(t, Task.Create(&sample)) + samples[name] = sample + } + + got, err := Task.List() + if err != nil { + t.Errorf(err.Error()) + } + if assert.FlatEqual(got, &samples) { + t.Errorf("Different response error. Got %v, expected %v", got, samples) + } + + for _, r := range samples { + assert.Must(t, Task.Delete(r.ID)) + } +} diff --git a/test/api/task/pkg.go b/test/api/task/pkg.go new file mode 100644 index 000000000..d32bde185 --- /dev/null +++ b/test/api/task/pkg.go @@ -0,0 +1,20 @@ +package task + +import ( + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + RichClient *binding.RichClient + Task binding.Task +) + + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Shortcut for Task-related RichClient methods. + Task = RichClient.Task +} diff --git a/test/api/task/samples.go b/test/api/task/samples.go new file mode 100644 index 000000000..0663c62de --- /dev/null +++ b/test/api/task/samples.go @@ -0,0 +1,15 @@ +package task + +import ( + "github.com/konveyor/tackle2-hub/api" +) + +// Set of valid resources for tests and reuse. +var ( + Windup = api.Task{ + Name: "Test windup task", + Addon: "windup", + Data: "{}", + } + Samples = []api.Task{Windup} +) diff --git a/test/assert/equality.go b/test/assert/equality.go new file mode 100644 index 000000000..cc860065e --- /dev/null +++ b/test/assert/equality.go @@ -0,0 +1,11 @@ +package assert + +import ( + "fmt" +) + +// +// Simple equality check working for flat types (no nested types passed by reference). +func FlatEqual(got, expected interface{}) bool { + return fmt.Sprintf("%v", got) == fmt.Sprintf("%v", expected) +} diff --git a/test/assert/error.go b/test/assert/error.go new file mode 100644 index 000000000..1316167c0 --- /dev/null +++ b/test/assert/error.go @@ -0,0 +1,23 @@ +package assert + +import ( + "testing" +) + +// +// Check error and if present, fail the test case. +// Examples usage: client.Should(t, task.Create(&r)) +func Should(t *testing.T, err error) { + if err != nil { + t.Errorf(err.Error()) + } +} + +// +// Check error and if present, fail and stop the test suite. +// Examples usage: client.Must(t, task.Create(&r)) +func Must(t *testing.T, err error) { + if err != nil { + t.Fatalf(err.Error()) + } +} diff --git a/test/integration/README.md b/test/integration/README.md new file mode 100644 index 000000000..005f71809 --- /dev/null +++ b/test/integration/README.md @@ -0,0 +1,5 @@ +# API Integration Tests + +Set of packages testing Hub functions together with other Konveyor components (requires full Konveyor installation to run). + +These test execution might take few minutes or more. diff --git a/test/integration/applications-inventory/analysis/pkg.go b/test/integration/applications-inventory/analysis/pkg.go new file mode 100644 index 000000000..49c9e19af --- /dev/null +++ b/test/integration/applications-inventory/analysis/pkg.go @@ -0,0 +1,35 @@ +package analysis + +import ( + "time" + + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/binding" + "github.com/konveyor/tackle2-hub/test/api/client" +) + +var ( + // Setup Hub API client + Client *binding.Client + RichClient *binding.RichClient + + // Analysis waiting loop 5 minutes (60 * 5s) + Retry = 60 + Wait = 5 * time.Second +) + +func init() { + // Prepare RichClient and login to Hub API (configured from env variables). + RichClient = client.PrepareRichClient() + + // Access REST client directly (some test API call need it) + Client = RichClient.Client() +} + +// Test cases for Application Analysis. +type TC struct { + Name string + Application api.Application + Task api.Task + TaskData string +} diff --git a/test/integration/applications-inventory/analysis/windup_basic_test.go b/test/integration/applications-inventory/analysis/windup_basic_test.go new file mode 100644 index 000000000..3591cc065 --- /dev/null +++ b/test/integration/applications-inventory/analysis/windup_basic_test.go @@ -0,0 +1,87 @@ +package analysis + +import ( + "encoding/json" + "testing" + "time" + + "github.com/konveyor/tackle2-hub/api" + "github.com/konveyor/tackle2-hub/test/api/application" + "github.com/konveyor/tackle2-hub/test/assert" +) + +// +// Test application analysis +// "Basic" means that there no other dependencies than the application itself (no need prepare credentials, proxy, etc) +func TestBasicAnalysis(t *testing.T) { + tests := []TC{ + { + Name: "Pathfinder cloud-readiness", + Application: application.PathfinderGit, + Task: api.Task{ + Addon: "windup", + State: "Ready", + }, + TaskData: `{ + "mode": { + "artifact": "", + "binary": false, + "withDeps": false, + "diva": true + }, + "output": "/windup/report", + "rules": { + "path": "", + "tags": { + "excluded": [ ] + } + }, + "scope": { + "packages": { + "excluded": [ ], + "included": [ ] + }, + "withKnown": false + }, + "sources": [ ], + "targets": [ + "cloud-readiness" + ] + }`, + }, + } + + // Test using "richclient" methods (preffered way). + for _, tc := range tests { + t.Run(tc.Name, func(t *testing.T) { + // Create the application. + assert.Should(t, RichClient.Application.Create(&tc.Application)) + + // Prepare and submit the analyze task. + json.Unmarshal([]byte(tc.TaskData), &tc.Task.Data) + tc.Task.Application = &api.Ref{ID: tc.Application.ID} + assert.Should(t, RichClient.Task.Create(&tc.Task)) + + // Wait until task finishes + var task *api.Task + var err error + for i := 0; i < Retry; i++ { + task, err = RichClient.Task.Get(tc.Task.ID) + if err != nil || task.State == "Succeeded" || task.State == "Failed" { + break + } + time.Sleep(Wait) + } + + if task.State != "Succeeded" { + t.Errorf("Analyze Task failed. Details: %+v", task) + } + + // TODO: check the report content here. + + // Cleanup. + assert.Must(t, RichClient.Application.Delete(tc.Application.ID)) + + }) + } +}