signoz/pkg/query-service/tests/integration/signoz_integrations_test.go
Raj Kamal Singh ab5285dee6
Feat: qs api integration connection status (#4628)
* chore: add integration attribs for connection tests and status

* chore: add connection status to integration details response

* chore: update integration lifecycle test to check for connection status too

* feat: add GetIntegrationConnectionTests to integrations manager and controller

* chore: add tests for querying integration connection status

* feat: add http API support for integration connection status

* chore: some cleanups

* chore: use PostableRule for integration alerts

* chore: some more cleanup
2024-03-05 15:23:56 +05:30

280 lines
8.4 KiB
Go

package tests
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"runtime/debug"
"testing"
mockhouse "github.com/srikanthccv/ClickHouse-go-mock"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/integrations"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/query-service/featureManager"
"go.signoz.io/signoz/pkg/query-service/model"
)
// Higher level tests for UI facing APIs
func TestSignozIntegrationLifeCycle(t *testing.T) {
require := require.New(t)
testbed := NewIntegrationsTestBed(t)
installedResp := testbed.GetInstalledIntegrationsFromQS()
require.Equal(
len(installedResp.Integrations), 0,
"no integrations should be installed at the beginning",
)
availableResp := testbed.GetAvailableIntegrationsFromQS()
availableIntegrations := availableResp.Integrations
require.Greater(
len(availableIntegrations), 0,
"some integrations should come bundled with SigNoz",
)
// Should be able to install integration
require.False(availableIntegrations[0].IsInstalled)
testbed.RequestQSToInstallIntegration(
availableIntegrations[0].Id, map[string]interface{}{},
)
testbed.mockLogQueryResponse([]model.SignozLog{})
ii := testbed.GetIntegrationDetailsFromQS(availableIntegrations[0].Id)
require.Equal(ii.Id, availableIntegrations[0].Id)
require.NotNil(ii.Installation)
require.NotNil(ii.ConnectionStatus)
require.Nil(ii.ConnectionStatus.Logs)
installedResp = testbed.GetInstalledIntegrationsFromQS()
installedIntegrations := installedResp.Integrations
require.Equal(len(installedIntegrations), 1)
require.Equal(installedIntegrations[0].Id, availableIntegrations[0].Id)
availableResp = testbed.GetAvailableIntegrationsFromQS()
availableIntegrations = availableResp.Integrations
require.Greater(len(availableIntegrations), 0)
// Integration connection status should get updated after signal data has been received.
testLog := makeTestSignozLog("test log body", map[string]interface{}{
"source": "nginx",
})
testbed.mockLogQueryResponse([]model.SignozLog{testLog})
connectionStatus := testbed.GetIntegrationConnectionStatus(ii.Id)
require.NotNil(connectionStatus)
require.NotNil(connectionStatus.Logs)
require.Equal(connectionStatus.Logs.LastReceivedTsMillis, int64(testLog.Timestamp/1000000))
testbed.mockLogQueryResponse([]model.SignozLog{testLog})
ii = testbed.GetIntegrationDetailsFromQS(ii.Id)
require.NotNil(ii.ConnectionStatus)
require.NotNil(ii.ConnectionStatus.Logs)
require.Equal(connectionStatus.Logs.LastReceivedTsMillis, int64(testLog.Timestamp/1000000))
// Should be able to uninstall integration
require.True(availableIntegrations[0].IsInstalled)
testbed.RequestQSToUninstallIntegration(
availableIntegrations[0].Id,
)
testbed.mockLogQueryResponse([]model.SignozLog{})
ii = testbed.GetIntegrationDetailsFromQS(availableIntegrations[0].Id)
require.Equal(ii.Id, availableIntegrations[0].Id)
require.Nil(ii.Installation)
installedResp = testbed.GetInstalledIntegrationsFromQS()
installedIntegrations = installedResp.Integrations
require.Equal(len(installedIntegrations), 0)
availableResp = testbed.GetAvailableIntegrationsFromQS()
availableIntegrations = availableResp.Integrations
require.Greater(len(availableIntegrations), 0)
require.False(availableIntegrations[0].IsInstalled)
}
type IntegrationsTestBed struct {
t *testing.T
testUser *model.User
qsHttpHandler http.Handler
mockClickhouse mockhouse.ClickConnMockCommon
}
func (tb *IntegrationsTestBed) GetAvailableIntegrationsFromQS() *integrations.IntegrationsListResponse {
result := tb.RequestQS("/api/v1/integrations", nil)
dataJson, err := json.Marshal(result.Data)
if err != nil {
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
}
var integrationsResp integrations.IntegrationsListResponse
err = json.Unmarshal(dataJson, &integrationsResp)
if err != nil {
tb.t.Fatalf("could not unmarshal apiResponse.Data json into PipelinesResponse")
}
return &integrationsResp
}
func (tb *IntegrationsTestBed) GetInstalledIntegrationsFromQS() *integrations.IntegrationsListResponse {
result := tb.RequestQS("/api/v1/integrations?is_installed=true", nil)
dataJson, err := json.Marshal(result.Data)
if err != nil {
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
}
var integrationsResp integrations.IntegrationsListResponse
err = json.Unmarshal(dataJson, &integrationsResp)
if err != nil {
tb.t.Fatalf("could not unmarshal apiResponse.Data json into PipelinesResponse")
}
return &integrationsResp
}
func (tb *IntegrationsTestBed) GetIntegrationDetailsFromQS(
integrationId string,
) *integrations.Integration {
result := tb.RequestQS(fmt.Sprintf(
"/api/v1/integrations/%s", integrationId,
), nil)
dataJson, err := json.Marshal(result.Data)
if err != nil {
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
}
var integrationResp integrations.Integration
err = json.Unmarshal(dataJson, &integrationResp)
if err != nil {
tb.t.Fatalf("could not unmarshal apiResponse.Data json")
}
return &integrationResp
}
func (tb *IntegrationsTestBed) GetIntegrationConnectionStatus(
integrationId string,
) *integrations.IntegrationConnectionStatus {
result := tb.RequestQS(fmt.Sprintf(
"/api/v1/integrations/%s/connection_status", integrationId,
), nil)
dataJson, err := json.Marshal(result.Data)
if err != nil {
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
}
var connectionStatus integrations.IntegrationConnectionStatus
err = json.Unmarshal(dataJson, &connectionStatus)
if err != nil {
tb.t.Fatalf("could not unmarshal apiResponse.Data json")
}
return &connectionStatus
}
func (tb *IntegrationsTestBed) RequestQSToInstallIntegration(
integrationId string, config map[string]interface{},
) {
request := integrations.InstallIntegrationRequest{
IntegrationId: integrationId,
Config: config,
}
tb.RequestQS("/api/v1/integrations/install", request)
}
func (tb *IntegrationsTestBed) RequestQSToUninstallIntegration(
integrationId string,
) {
request := integrations.UninstallIntegrationRequest{
IntegrationId: integrationId,
}
tb.RequestQS("/api/v1/integrations/uninstall", request)
}
func (tb *IntegrationsTestBed) RequestQS(
path string,
postData interface{},
) *app.ApiResponse {
req, err := NewAuthenticatedTestRequest(
tb.testUser, path, postData,
)
if err != nil {
tb.t.Fatalf("couldn't create authenticated test request: %v", err)
}
respWriter := httptest.NewRecorder()
tb.qsHttpHandler.ServeHTTP(respWriter, req)
response := respWriter.Result()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
tb.t.Fatalf("couldn't read response body received from QS: %v", err)
}
if response.StatusCode != 200 {
tb.t.Fatalf(
"unexpected response status from query service for path %s. status: %d, body: %v\n%v",
path, response.StatusCode, string(responseBody), string(debug.Stack()),
)
}
var result app.ApiResponse
err = json.Unmarshal(responseBody, &result)
if err != nil {
tb.t.Fatalf(
"Could not unmarshal QS response into an ApiResponse.\nResponse body: %s",
string(responseBody),
)
}
return &result
}
func (tb *IntegrationsTestBed) mockLogQueryResponse(logsInResponse []model.SignozLog) {
addLogsQueryExpectation(tb.mockClickhouse, logsInResponse)
}
func NewIntegrationsTestBed(t *testing.T) *IntegrationsTestBed {
testDB, testDBFilePath := integrations.NewTestSqliteDB(t)
// TODO(Raj): This should not require passing in the DB file path
dao.InitDao("sqlite", testDBFilePath)
controller, err := integrations.NewController(testDB)
if err != nil {
t.Fatalf("could not create integrations controller: %v", err)
}
fm := featureManager.StartManager()
reader, mockClickhouse := NewMockClickhouseReader(t, testDB, fm)
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
Reader: reader,
AppDao: dao.DB(),
IntegrationsController: controller,
FeatureFlags: fm,
})
if err != nil {
t.Fatalf("could not create a new ApiHandler: %v", err)
}
router := app.NewRouter()
am := app.NewAuthMiddleware(auth.GetUserFromRequest)
apiHandler.RegisterIntegrationRoutes(router, am)
user, apiErr := createTestUser()
if apiErr != nil {
t.Fatalf("could not create a test user: %v", apiErr)
}
return &IntegrationsTestBed{
t: t,
testUser: user,
qsHttpHandler: router,
mockClickhouse: mockClickhouse,
}
}