sourcegraph/enterprise/cmd/frontend/internal/executorqueue/handler/handler_test.go
Randell Callahan 5a3fda4bd6
executors: Provide more context when the semantic version is invalid (#55024)
The annoying error of `Invalid Semantic Version` tells us nothing and
makes troubleshooting the issue difficult with lots of churn.

I have update Executor usage of `semvar.NewVersion` to wrap the error
with a message that include the actual version that causes the error.
This will help us understand the specific version string that is causing
the error and more quickly determine _why_ the version is getting
injected.

## Test plan

Update/add Go tests.
2023-07-17 13:59:46 -06:00

981 lines
43 KiB
Go

package handler_test
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/sourcegraph/sourcegraph/enterprise/cmd/frontend/internal/executorqueue/handler"
"github.com/sourcegraph/sourcegraph/internal/database"
internalexecutor "github.com/sourcegraph/sourcegraph/internal/executor"
executorstore "github.com/sourcegraph/sourcegraph/internal/executor/store"
executortypes "github.com/sourcegraph/sourcegraph/internal/executor/types"
metricsstore "github.com/sourcegraph/sourcegraph/internal/metrics/store"
dbworkerstore "github.com/sourcegraph/sourcegraph/internal/workerutil/dbworker/store"
dbworkerstoremocks "github.com/sourcegraph/sourcegraph/internal/workerutil/dbworker/store/mocks"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/lib/pointers"
)
func TestHandler_Name(t *testing.T) {
queueHandler := handler.QueueHandler[testRecord]{Name: "test"}
h := handler.NewHandler(
database.NewMockExecutorStore(),
executorstore.NewMockJobTokenStore(),
metricsstore.NewMockDistributedStore(),
queueHandler,
)
assert.Equal(t, "test", h.Name())
}
func TestHandler_HandleDequeue(t *testing.T) {
tests := []struct {
name string
body string
transformerFunc handler.TransformerFunc[testRecord]
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore)
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore)
}{
{
name: "Dequeue record",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{ID: record.RecordID()}, nil
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
jobTokenStore.CreateFunc.PushReturn("sometoken", nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"id":1,"token":"sometoken","repositoryName":"","repositoryDirectory":"","commit":"","fetchTags":false,"shallowClone":false,"sparseCheckout":null,"files":{},"dockerSteps":null,"cliSteps":null,"redactedValues":null}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
assert.Equal(t, "test-executor", mockStore.DequeueFunc.History()[0].Arg1)
assert.Nil(t, mockStore.DequeueFunc.History()[0].Arg2)
require.Len(t, jobTokenStore.CreateFunc.History(), 1)
assert.Equal(t, 1, jobTokenStore.CreateFunc.History()[0].Arg1)
assert.Equal(t, "test", jobTokenStore.CreateFunc.History()[0].Arg2)
},
},
{
name: "Invalid version",
body: `{"executorName": "test-executor", "version":"\n1.2", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"failed to check version \"\\n1.2\": Invalid Semantic Version"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 0)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)
},
},
{
name: "Dequeue error",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{}, false, errors.New("failed to dequeue"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.Dequeue: failed to dequeue"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)
},
},
{
name: "Nothing to dequeue",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{}, false, nil)
},
expectedStatusCode: http.StatusNoContent,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)
},
},
{
name: "Failed to transform record",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{}, errors.New("failed")
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
mockStore.MarkFailedFunc.PushReturn(true, nil)
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"RecordTransformer: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
assert.Equal(t, 1, mockStore.MarkFailedFunc.History()[0].Arg1)
assert.Equal(t, "failed to transform record: failed", mockStore.MarkFailedFunc.History()[0].Arg2)
assert.Equal(t, dbworkerstore.MarkFinalOptions{}, mockStore.MarkFailedFunc.History()[0].Arg3)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)
},
},
{
name: "Failed to mark record as failed",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{}, errors.New("failed")
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
mockStore.MarkFailedFunc.PushReturn(false, errors.New("failed to mark"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"RecordTransformer: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
assert.Equal(t, 1, mockStore.MarkFailedFunc.History()[0].Arg1)
assert.Equal(t, "failed to transform record: failed", mockStore.MarkFailedFunc.History()[0].Arg2)
assert.Equal(t, dbworkerstore.MarkFinalOptions{}, mockStore.MarkFailedFunc.History()[0].Arg3)
require.Len(t, jobTokenStore.CreateFunc.History(), 0)
},
},
{
name: "V2 job",
body: `{"executorName": "test-executor", "version": "dev", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{ID: record.RecordID()}, nil
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
jobTokenStore.CreateFunc.PushReturn("sometoken", nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"version":2,"id":1,"token":"sometoken","repositoryName":"","repositoryDirectory":"","commit":"","fetchTags":false,"shallowClone":false,"sparseCheckout":null,"files":{},"dockerSteps":null,"cliSteps":null,"redactedValues":null,"dockerAuthConfig":{}}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 1)
},
},
{
name: "Failed to create job token",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{ID: record.RecordID()}, nil
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
jobTokenStore.CreateFunc.PushReturn("", errors.New("failed to create token"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"CreateToken: failed to create token"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 1)
require.Len(t, jobTokenStore.RegenerateFunc.History(), 0)
},
},
{
name: "Job token already exists",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{ID: record.RecordID()}, nil
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
jobTokenStore.CreateFunc.PushReturn("", executorstore.ErrJobTokenAlreadyCreated)
jobTokenStore.RegenerateFunc.PushReturn("somenewtoken", nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"id":1,"token":"somenewtoken","repositoryName":"","repositoryDirectory":"","commit":"","fetchTags":false,"shallowClone":false,"sparseCheckout":null,"files":{},"dockerSteps":null,"cliSteps":null,"redactedValues":null}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 1)
require.Len(t, jobTokenStore.RegenerateFunc.History(), 1)
assert.Equal(t, 1, jobTokenStore.RegenerateFunc.History()[0].Arg1)
assert.Equal(t, "test", jobTokenStore.RegenerateFunc.History()[0].Arg2)
},
},
{
name: "Failed to regenerate token",
body: `{"executorName": "test-executor", "numCPUs": 1, "memory": "1GB", "diskSpace": "10GB"}`,
transformerFunc: func(ctx context.Context, version string, record testRecord, resourceMetadata handler.ResourceMetadata) (executortypes.Job, error) {
return executortypes.Job{ID: record.RecordID()}, nil
},
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
mockStore.DequeueFunc.PushReturn(testRecord{id: 1}, true, nil)
jobTokenStore.CreateFunc.PushReturn("", executorstore.ErrJobTokenAlreadyCreated)
jobTokenStore.RegenerateFunc.PushReturn("", errors.New("failed to regen token"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"RegenerateToken: failed to regen token"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], jobTokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.DequeueFunc.History(), 1)
require.Len(t, jobTokenStore.CreateFunc.History(), 1)
require.Len(t, jobTokenStore.RegenerateFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
jobTokenStore := executorstore.NewMockJobTokenStore()
h := handler.NewHandler(
database.NewMockExecutorStore(),
jobTokenStore,
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore, RecordTransformer: test.transformerFunc},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleDequeue)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore, jobTokenStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore, jobTokenStore)
}
})
}
}
func TestHandler_HandleAddExecutionLogEntry(t *testing.T) {
startTime := time.Date(2023, 1, 2, 3, 4, 5, 0, time.UTC)
tests := []struct {
name string
body string
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord])
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord])
}{
{
name: "Add execution log entry",
body: fmt.Sprintf(`{"executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.AddExecutionLogEntryFunc.PushReturn(10, nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `10`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.AddExecutionLogEntryFunc.History(), 1)
assert.Equal(t, 42, mockStore.AddExecutionLogEntryFunc.History()[0].Arg1)
assert.Equal(
t,
internalexecutor.ExecutionLogEntry{
Key: "foo",
Command: []string{"faz", "baz"},
StartTime: startTime,
ExitCode: pointers.Ptr(0),
Out: "done",
DurationMs: pointers.Ptr(100),
},
mockStore.AddExecutionLogEntryFunc.History()[0].Arg2,
)
assert.Equal(
t,
dbworkerstore.ExecutionLogEntryOptions{WorkerHostname: "test-executor", State: "processing"},
mockStore.AddExecutionLogEntryFunc.History()[0].Arg3,
)
},
},
{
name: "Log entry not added",
body: fmt.Sprintf(`{"executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.AddExecutionLogEntryFunc.PushReturn(0, errors.New("failed to add"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.AddExecutionLogEntry: failed to add"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.AddExecutionLogEntryFunc.History(), 1)
},
},
{
name: "Unknown job",
body: fmt.Sprintf(`{"executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.AddExecutionLogEntryFunc.PushReturn(0, dbworkerstore.ErrExecutionLogEntryNotUpdated)
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"unknown job"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.AddExecutionLogEntryFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
h := handler.NewHandler(
database.NewMockExecutorStore(),
executorstore.NewMockJobTokenStore(),
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleAddExecutionLogEntry)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore)
}
})
}
}
func TestHandler_HandleUpdateExecutionLogEntry(t *testing.T) {
startTime := time.Date(2023, 1, 2, 3, 4, 5, 0, time.UTC)
tests := []struct {
name string
body string
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord])
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord])
}{
{
name: "Update execution log entry",
body: fmt.Sprintf(`{"entryId": 10, "executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.UpdateExecutionLogEntryFunc.PushReturn(nil)
},
expectedStatusCode: http.StatusNoContent,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.UpdateExecutionLogEntryFunc.History(), 1)
assert.Equal(t, 42, mockStore.UpdateExecutionLogEntryFunc.History()[0].Arg1)
assert.Equal(t, 10, mockStore.UpdateExecutionLogEntryFunc.History()[0].Arg2)
assert.Equal(
t,
internalexecutor.ExecutionLogEntry{
Key: "foo",
Command: []string{"faz", "baz"},
StartTime: startTime,
ExitCode: pointers.Ptr(0),
Out: "done",
DurationMs: pointers.Ptr(100),
},
mockStore.UpdateExecutionLogEntryFunc.History()[0].Arg3,
)
assert.Equal(
t,
dbworkerstore.ExecutionLogEntryOptions{WorkerHostname: "test-executor", State: "processing"},
mockStore.UpdateExecutionLogEntryFunc.History()[0].Arg4,
)
},
},
{
name: "Log entry not updated",
body: fmt.Sprintf(`{"entryId": 10, "executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.UpdateExecutionLogEntryFunc.PushReturn(errors.New("failed to update"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.UpdateExecutionLogEntry: failed to update"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.UpdateExecutionLogEntryFunc.History(), 1)
},
},
{
name: "Unknown job",
body: fmt.Sprintf(`{"entryId": 10, "executorName": "test-executor", "jobId": 42, "key": "foo", "command": ["faz", "baz"], "startTime": "%s", "exitCode": 0, "out": "done", "durationMs":100}`, startTime.Format(time.RFC3339)),
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord]) {
mockStore.UpdateExecutionLogEntryFunc.PushReturn(dbworkerstore.ErrExecutionLogEntryNotUpdated)
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"unknown job"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, mockStore.UpdateExecutionLogEntryFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
h := handler.NewHandler(
database.NewMockExecutorStore(),
executorstore.NewMockJobTokenStore(),
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleUpdateExecutionLogEntry)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore)
}
})
}
}
func TestHandler_HandleMarkComplete(t *testing.T) {
tests := []struct {
name string
body string
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
}{
{
name: "Mark complete",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkCompleteFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(nil)
},
expectedStatusCode: http.StatusNoContent,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkCompleteFunc.History(), 1)
assert.Equal(t, 42, mockStore.MarkCompleteFunc.History()[0].Arg1)
assert.Equal(t, dbworkerstore.MarkFinalOptions{WorkerHostname: "test-executor"}, mockStore.MarkCompleteFunc.History()[0].Arg2)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
assert.Equal(t, 42, tokenStore.DeleteFunc.History()[0].Arg1)
assert.Equal(t, "test", tokenStore.DeleteFunc.History()[0].Arg2)
},
},
{
name: "Failed to mark complete",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkCompleteFunc.PushReturn(false, errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.MarkComplete: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkCompleteFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Unknown job",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkCompleteFunc.PushReturn(false, nil)
},
expectedStatusCode: http.StatusNotFound,
expectedResponseBody: `null`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkCompleteFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Failed to delete job token",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkCompleteFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"jobTokenStore.Delete: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkCompleteFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
tokenStore := executorstore.NewMockJobTokenStore()
h := handler.NewHandler(
database.NewMockExecutorStore(),
tokenStore,
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleMarkComplete)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore, tokenStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore, tokenStore)
}
})
}
}
func TestHandler_HandleMarkErrored(t *testing.T) {
tests := []struct {
name string
body string
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
}{
{
name: "Mark errored",
body: `{"executorName": "test-executor", "jobId": 42, "errorMessage": "it failed"}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkErroredFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(nil)
},
expectedStatusCode: http.StatusNoContent,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkErroredFunc.History(), 1)
assert.Equal(t, 42, mockStore.MarkErroredFunc.History()[0].Arg1)
assert.Equal(t, "it failed", mockStore.MarkErroredFunc.History()[0].Arg2)
assert.Equal(t, dbworkerstore.MarkFinalOptions{WorkerHostname: "test-executor"}, mockStore.MarkErroredFunc.History()[0].Arg3)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
assert.Equal(t, 42, tokenStore.DeleteFunc.History()[0].Arg1)
assert.Equal(t, "test", tokenStore.DeleteFunc.History()[0].Arg2)
},
},
{
name: "Failed to mark errored",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkErroredFunc.PushReturn(false, errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.MarkErrored: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkErroredFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Unknown job",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkErroredFunc.PushReturn(false, nil)
},
expectedStatusCode: http.StatusNotFound,
expectedResponseBody: `null`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkErroredFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Failed to delete job token",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkErroredFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"jobTokenStore.Delete: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkErroredFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
tokenStore := executorstore.NewMockJobTokenStore()
h := handler.NewHandler(
database.NewMockExecutorStore(),
tokenStore,
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleMarkErrored)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore, tokenStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore, tokenStore)
}
})
}
}
func TestHandler_HandleMarkFailed(t *testing.T) {
tests := []struct {
name string
body string
mockFunc func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore)
}{
{
name: "Mark failed",
body: `{"executorName": "test-executor", "jobId": 42, "errorMessage": "it failed"}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkFailedFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(nil)
},
expectedStatusCode: http.StatusNoContent,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
assert.Equal(t, 42, mockStore.MarkFailedFunc.History()[0].Arg1)
assert.Equal(t, "it failed", mockStore.MarkFailedFunc.History()[0].Arg2)
assert.Equal(t, dbworkerstore.MarkFinalOptions{WorkerHostname: "test-executor"}, mockStore.MarkFailedFunc.History()[0].Arg3)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
assert.Equal(t, 42, tokenStore.DeleteFunc.History()[0].Arg1)
assert.Equal(t, "test", tokenStore.DeleteFunc.History()[0].Arg2)
},
},
{
name: "Failed to mark failed",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkFailedFunc.PushReturn(false, errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.MarkFailed: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Unknown job",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkErroredFunc.PushReturn(false, nil)
},
expectedStatusCode: http.StatusNotFound,
expectedResponseBody: `null`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 0)
},
},
{
name: "Failed to delete job token",
body: `{"executorName": "test-executor", "jobId": 42}`,
mockFunc: func(mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
mockStore.MarkFailedFunc.PushReturn(true, nil)
tokenStore.DeleteFunc.PushReturn(errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"jobTokenStore.Delete: failed"}`,
assertionFunc: func(t *testing.T, mockStore *dbworkerstoremocks.MockStore[testRecord], tokenStore *executorstore.MockJobTokenStore) {
require.Len(t, mockStore.MarkFailedFunc.History(), 1)
require.Len(t, tokenStore.DeleteFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
tokenStore := executorstore.NewMockJobTokenStore()
h := handler.NewHandler(
database.NewMockExecutorStore(),
tokenStore,
metricsstore.NewMockDistributedStore(),
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleMarkFailed)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(mockStore, tokenStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, mockStore, tokenStore)
}
})
}
}
func TestHandler_HandleHeartbeat(t *testing.T) {
tests := []struct {
name string
body string
mockFunc func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord])
expectedStatusCode int
expectedResponseBody string
assertionFunc func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord])
}{
{
name: "V2 Heartbeat number IDs",
body: `{"version":"V2", "executorName": "test-executor", "jobIds": [42, 7], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
mockFunc: func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
executorStore.UpsertHeartbeatFunc.PushReturn(nil)
mockStore.HeartbeatFunc.PushReturn([]string{"42", "7"}, nil, nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"knownIds":["42","7"],"cancelIds":null}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 1)
require.Len(t, mockStore.HeartbeatFunc.History(), 1)
},
},
{
name: "V2 Heartbeat",
body: `{"version":"V2", "executorName": "test-executor", "jobIds": ["42", "7"], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
mockFunc: func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
executorStore.UpsertHeartbeatFunc.PushReturn(nil)
mockStore.HeartbeatFunc.PushReturn([]string{"42", "7"}, nil, nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"knownIds":["42","7"],"cancelIds":null}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 1)
require.Len(t, mockStore.HeartbeatFunc.History(), 1)
},
},
{
name: "Invalid worker hostname",
body: `{"executorName": "", "jobIds": ["42", "7"], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"worker hostname cannot be empty"}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 0)
require.Len(t, mockStore.HeartbeatFunc.History(), 0)
},
},
{
name: "Failed to upsert heartbeat",
body: `{"executorName": "test-executor", "jobIds": ["42", "7"], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
mockFunc: func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
executorStore.UpsertHeartbeatFunc.PushReturn(errors.New("failed"))
mockStore.HeartbeatFunc.PushReturn([]string{"42", "7"}, nil, nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"knownIds":["42","7"],"cancelIds":null}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 1)
require.Len(t, mockStore.HeartbeatFunc.History(), 1)
},
},
{
name: "Failed to heartbeat",
body: `{"executorName": "test-executor", "jobIds": ["42", "7"], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
mockFunc: func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
executorStore.UpsertHeartbeatFunc.PushReturn(nil)
mockStore.HeartbeatFunc.PushReturn(nil, nil, errors.New("failed"))
},
expectedStatusCode: http.StatusInternalServerError,
expectedResponseBody: `{"error":"dbworkerstore.UpsertHeartbeat: failed"}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 1)
require.Len(t, mockStore.HeartbeatFunc.History(), 1)
},
},
{
name: "V2 has cancelled ids",
body: `{"version": "V2", "executorName": "test-executor", "jobIds": ["42", "7"], "os": "test-os", "architecture": "test-arch", "dockerVersion": "1.0", "executorVersion": "2.0", "gitVersion": "3.0", "igniteVersion": "4.0", "srcCliVersion": "5.0", "prometheusMetrics": ""}`,
mockFunc: func(metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
executorStore.UpsertHeartbeatFunc.PushReturn(nil)
mockStore.HeartbeatFunc.PushReturn(nil, []string{"42", "7"}, nil)
},
expectedStatusCode: http.StatusOK,
expectedResponseBody: `{"knownIds":null,"cancelIds":["42","7"]}`,
assertionFunc: func(t *testing.T, metricsStore *metricsstore.MockDistributedStore, executorStore *database.MockExecutorStore, mockStore *dbworkerstoremocks.MockStore[testRecord]) {
require.Len(t, executorStore.UpsertHeartbeatFunc.History(), 1)
require.Len(t, mockStore.HeartbeatFunc.History(), 1)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockStore := dbworkerstoremocks.NewMockStore[testRecord]()
executorStore := database.NewMockExecutorStore()
metricsStore := metricsstore.NewMockDistributedStore()
h := handler.NewHandler(
executorStore,
executorstore.NewMockJobTokenStore(),
metricsStore,
handler.QueueHandler[testRecord]{Store: mockStore},
)
router := mux.NewRouter()
router.HandleFunc("/{queueName}", h.HandleHeartbeat)
req, err := http.NewRequest(http.MethodPost, "/test", strings.NewReader(test.body))
require.NoError(t, err)
rw := httptest.NewRecorder()
if test.mockFunc != nil {
test.mockFunc(metricsStore, executorStore, mockStore)
}
router.ServeHTTP(rw, req)
assert.Equal(t, test.expectedStatusCode, rw.Code)
b, err := io.ReadAll(rw.Body)
require.NoError(t, err)
if len(test.expectedResponseBody) > 0 {
assert.JSONEq(t, test.expectedResponseBody, string(b))
} else {
assert.Empty(t, string(b))
}
if test.assertionFunc != nil {
test.assertionFunc(t, metricsStore, executorStore, mockStore)
}
})
}
}
// TODO: add test for prometheus metrics. At the moment, encode will create a string with newlines that causes the
// json decoder to fail. So... come back to this later...
func encodeMetrics(t *testing.T, data ...*dto.MetricFamily) string {
var buf bytes.Buffer
enc := expfmt.NewEncoder(&buf, expfmt.FmtText)
for _, d := range data {
err := enc.Encode(d)
require.NoError(t, err)
}
return buf.String()
}
type testRecord struct {
id int
}
func (r testRecord) RecordID() int { return r.id }
func (r testRecord) RecordUID() string {
return strconv.Itoa(r.id)
}