-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a mock fleet server for integration tests (#2695)
- Loading branch information
Showing
7 changed files
with
1,121 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Mock Fleet Server | ||
|
||
It's mock for fleet-server allowing to test the Agent interactions with | ||
fleet-server without the need of running a fleet-server and having full | ||
control of it to test even edge cases such as error handling. | ||
|
||
## tl;dr | ||
|
||
- See [`fleetservertest_test.go`](fleetserver_test.go) for examples. | ||
|
||
- `fleetservertest.API` defines a `handlernameFn` property for each available handlers. By default, any not implemented handler returns a `http.StatusNotImplemented`. | ||
|
||
- Use `fleetservertest.NewServer(fleetservertest.API{})` to create a new test server. It's a `*httptest.Server`: | ||
|
||
```go | ||
NewServer(API{ | ||
AckFn: nil, | ||
CheckinFn: nil, | ||
EnrollFn: nil, | ||
ArtifactFn: nil, | ||
StatusFn: nil, | ||
UploadBeginFn: nil, | ||
UploadChunkFn: nil, | ||
UploadCompleteFn: nil, | ||
}) | ||
``` | ||
|
||
- Use the `fleetservertest.NewPATHNAME(args)` functions to get a path ready to be used: | ||
```go | ||
p := NewPathAgentAcks("my-agent-id") | ||
// p = "/api/fleet/agents/my-agent-id/acks" | ||
``` | ||
|
||
- Use `fleetservertest.NewHANDERNAME()` to get a ready to use handler: | ||
```go | ||
ts := fleetservertest.NewServer(API{ | ||
CheckinFn: fleetservertest.NewCheckinHandler("agentID", "ackToken", false), | ||
}) | ||
``` | ||
|
||
- Check [`handlers.go`](handlers.go) for the available paths and handlers. | ||
- Check [`models.go`](models.go) for the request and response models or the [openapi](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/fleet-server/main/model/openapi.yml#/) definition. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package fleetservertest | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"net/http/httptest" | ||
) | ||
|
||
// API holds the handlers for the fleet-api, see https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/fleet-server/main/model/openapi.yml | ||
// for rendered OpenAPI definition. If any of the handlers are nil, a | ||
// http.StatusNotImplemented is returned for the route. | ||
type API struct { | ||
AckFn func( | ||
ctx context.Context, | ||
id string, | ||
ackRequest AckRequest) (*AckResponse, *HTTPError) | ||
|
||
CheckinFn func( | ||
ctx context.Context, | ||
id string, | ||
userAgent string, | ||
acceptEncoding string, | ||
checkinRequest CheckinRequest) (*CheckinResponse, *HTTPError) | ||
|
||
EnrollFn func( | ||
ctx context.Context, | ||
id string, | ||
userAgent string, | ||
enrollRequest EnrollRequest) (*EnrollResponse, *HTTPError) | ||
|
||
ArtifactFn func( | ||
ctx context.Context, | ||
id string, | ||
sha2 string) *HTTPError | ||
|
||
StatusFn func( | ||
ctx context.Context) (*StatusResponse, *HTTPError) | ||
|
||
UploadBeginFn func( | ||
ctx context.Context, | ||
requestBody UploadBeginRequest) (*UploadBeginResponse, *HTTPError) | ||
|
||
UploadChunkFn func( | ||
ctx context.Context, | ||
id string, | ||
chunkNum int32, | ||
xChunkSHA2 string, | ||
body io.ReadCloser) *HTTPError | ||
|
||
UploadCompleteFn func( | ||
ctx context.Context, | ||
id string, | ||
uploadCompleteRequest UploadCompleteRequest) *HTTPError | ||
} | ||
|
||
// NewServer returns a new started *httptest.Server mocking the Fleet Server API. | ||
// If a route is called and its handler (the *Fn field) is nil a. | ||
// http.StatusNotImplemented error will be returned. | ||
func NewServer(api API) *httptest.Server { | ||
mux := NewRouter(Handlers{api: api}) | ||
|
||
return httptest.NewServer(mux) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package fleetservertest | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/elastic/elastic-agent/internal/pkg/fleetapi" | ||
) | ||
|
||
func ExampleNewServer_status() { | ||
ts := NewServer(API{ | ||
StatusFn: NewStatusHandlerHealth(), | ||
}) | ||
|
||
resp, err := http.Get(ts.URL + PathStatus) //nolint:noctx // it's fine on a test | ||
if err != nil { | ||
panic(fmt.Sprintf("could not make request to fleet-test-server: %v", err)) | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
if err != nil { | ||
panic(fmt.Sprintf("could not read response: %v", err)) | ||
} | ||
} | ||
fmt.Printf("%s", body) | ||
|
||
// Output: | ||
// {"name":"fleet-server","status":"HEALTHY"} | ||
} | ||
|
||
func ExampleNewServer_checkin() { | ||
agentID := "agentID" | ||
|
||
ts := NewServer(API{ | ||
CheckinFn: NewCheckinHandler(agentID, "", false), | ||
}) | ||
|
||
cmd := fleetapi.NewCheckinCmd( | ||
agentInfo(agentID), sender{url: ts.URL, path: NewPathCheckin(agentID)}) | ||
resp, _, err := cmd.Execute(context.Background(), &fleetapi.CheckinRequest{}) | ||
if err != nil { | ||
panic(fmt.Sprintf("failed executing checkin: %v", err)) | ||
} | ||
|
||
fmt.Println(resp.Actions) | ||
|
||
// Output: | ||
// [action_id: policy:24e4d030-ffa7-11ed-b040-9debaa5fecb8:2:1, type: POLICY_CHANGE] | ||
} | ||
|
||
type agentInfo string | ||
|
||
func (a agentInfo) AgentID() string { | ||
return "" | ||
} | ||
|
||
type sender struct { | ||
url, path string | ||
} | ||
|
||
func (s sender) Send( | ||
ctx context.Context, | ||
method string, | ||
path string, | ||
params url.Values, | ||
headers http.Header, | ||
body io.Reader) (*http.Response, error) { | ||
return &http.Response{ | ||
Status: http.StatusText(http.StatusOK), | ||
StatusCode: http.StatusOK, | ||
Body: io.NopCloser(strings.NewReader( | ||
checkinResponseJSONPolicySystemIntegration)), | ||
}, nil | ||
} | ||
|
||
func (s sender) URI() string { | ||
return s.url + s.path | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package fleetservertest | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
const ( | ||
PathAgentAcks = "/api/fleet/agents/{id}/acks" | ||
PathAgentCheckin = "/api/fleet/agents/{id}/checkin" | ||
PathAgentEnroll = "/api/fleet/agents/{id}" | ||
|
||
PathArtifact = "/api/fleet/artifacts/{id}/{sha2}" | ||
PathStatus = "/api/status" | ||
|
||
PathUploadBegin = "/api/fleet/uploads" | ||
PathUploadChunk = "/api/fleet/uploads/{id}/{chunkNum}" | ||
PathUploadComplete = "/api/fleet/uploads/{id}" | ||
) | ||
|
||
func NewPathAgentAcks(agentID string) string { | ||
return strings.Replace(PathAgentAcks, "{id}", agentID, 1) | ||
} | ||
|
||
func NewPathCheckin(agentID string) string { | ||
return strings.Replace(PathAgentCheckin, "{id}", agentID, 1) | ||
} | ||
|
||
func NewPathAgentEnroll(agentID string) string { | ||
return strings.Replace(PathAgentEnroll, "{id}", agentID, 1) | ||
} | ||
|
||
func NewPathArtifact(agentID, sha2 string) string { | ||
return strings.Replace( | ||
strings.Replace(PathArtifact, "{id}", agentID, 1), | ||
"{sha2}", | ||
sha2, | ||
1) | ||
} | ||
|
||
func NewPathUploadBegin() string { | ||
return PathUploadBegin | ||
} | ||
|
||
func NewPathUploadChunk(agentID, chunkNum string) string { | ||
return strings.Replace( | ||
strings.Replace(PathUploadChunk, "{id}", agentID, 1), | ||
"{chunkNum}", | ||
chunkNum, | ||
1) | ||
} | ||
|
||
func NewPathUploadComplete(agentID string) string { | ||
return strings.Replace(PathUploadComplete, "{id}", agentID, 1) | ||
} | ||
|
||
func NewCheckinHandler(agentID, ackToken string, withEndpoint bool) func( | ||
ctx context.Context, | ||
id string, | ||
userAgent string, | ||
acceptEncoding string, | ||
checkinRequest CheckinRequest) (*CheckinResponse, *HTTPError) { | ||
|
||
policy := checkinResponseJSONPolicySystemIntegration | ||
if withEndpoint { | ||
policy = checkinResponseJSONPolicySystemIntegrationAndEndpoint | ||
} | ||
|
||
return func( | ||
ctx context.Context, | ||
id string, | ||
userAgent string, | ||
acceptEncoding string, | ||
checkinRequest CheckinRequest) (*CheckinResponse, *HTTPError) { | ||
|
||
resp := CheckinResponse{} | ||
err := json.Unmarshal( | ||
[]byte(fmt.Sprintf(policy, ackToken, agentID)), | ||
&resp) | ||
if err != nil { | ||
return nil, &HTTPError{ | ||
StatusCode: http.StatusInternalServerError, | ||
Error: err.Error(), | ||
Message: "failed to unmarshal policy", | ||
} | ||
} | ||
|
||
return &resp, nil | ||
} | ||
} | ||
|
||
func NewStatusHandlerHealth() func(ctx context.Context) (*StatusResponse, *HTTPError) { | ||
return func(ctx context.Context) (*StatusResponse, *HTTPError) { | ||
return &StatusResponse{ | ||
Name: "fleet-server", | ||
Status: "HEALTHY", | ||
// fleet-server does not respond with version information | ||
}, nil | ||
} | ||
} | ||
|
||
func NewStatusHandlerUnhealth() func(ctx context.Context) (*StatusResponse, *HTTPError) { | ||
return func(ctx context.Context) (*StatusResponse, *HTTPError) { | ||
return &StatusResponse{ | ||
Name: "fleet-server", | ||
Status: "UNHEALTHY", | ||
// fleet-server does not respond with version information | ||
}, &HTTPError{StatusCode: http.StatusInternalServerError} | ||
} | ||
} |
Oops, something went wrong.