mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-15 01:28:35 +00:00

# Which Problems Are Solved The production endpoint of the service ping was wrong. Additionally we discussed in the sprint review, that we could randomize the default interval to prevent all systems to report data at the very same time and also require a minimal interval. # How the Problems Are Solved - fixed the endpoint - If the interval is set to @daily (default), we generate a random time (minute, hour) as a cron format. - Check if the interval is more than 30min and return an error if not. - Fixed yaml indent on `ResourceCount` # Additional Changes None # Additional Context as discussed internally
1127 lines
31 KiB
Go
1127 lines
31 KiB
Go
package serviceping
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
"github.com/riverqueue/river"
|
|
"github.com/stretchr/testify/assert"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
"github.com/zitadel/zitadel/internal/queue"
|
|
"github.com/zitadel/zitadel/internal/serviceping/mock"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
analytics "github.com/zitadel/zitadel/pkg/grpc/analytics/v2beta"
|
|
)
|
|
|
|
var (
|
|
testNow = time.Now()
|
|
errInsert = fmt.Errorf("insert error")
|
|
)
|
|
|
|
func TestWorker_reportBaseInformation(t *testing.T) {
|
|
type fields struct {
|
|
reportClient func(*testing.T) analytics.TelemetryServiceClient
|
|
db func(*testing.T) Queries
|
|
systemID string
|
|
version string
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
}
|
|
type want struct {
|
|
reportID string
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
name: "database error, error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
ctrl := gomock.NewController(t)
|
|
queries := mock.NewMockQueries(ctrl)
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
nil, zerrors.ThrowInternal(nil, "id", "db error"),
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
return mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
},
|
|
},
|
|
want: want{
|
|
reportID: "",
|
|
err: zerrors.ThrowInternal(nil, "id", "db error"),
|
|
},
|
|
},
|
|
{
|
|
name: "telemetry client error, error",
|
|
fields: fields{
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain", "domain2"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
nil, status.Error(codes.Internal, "error"),
|
|
)
|
|
return client
|
|
},
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
{
|
|
Domain: "domain2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
return queries
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
want: want{
|
|
reportID: "",
|
|
err: status.Error(codes.Internal, "error"),
|
|
},
|
|
},
|
|
{
|
|
name: "report ok, reportID returned",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
{
|
|
Domain: "domain2",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain", "domain2"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportBaseInformationResponse{ReportId: "report-id"}, nil,
|
|
)
|
|
return client
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
want: want{
|
|
reportID: "report-id",
|
|
err: nil,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &Worker{
|
|
reportClient: tt.fields.reportClient(t),
|
|
db: tt.fields.db(t),
|
|
systemID: tt.fields.systemID,
|
|
version: tt.fields.version,
|
|
}
|
|
got, err := w.reportBaseInformation(tt.args.ctx)
|
|
assert.Equal(t, tt.want.reportID, got)
|
|
assert.ErrorIs(t, err, tt.want.err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorker_reportResourceCounts(t *testing.T) {
|
|
type fields struct {
|
|
reportClient func(*testing.T) analytics.TelemetryServiceClient
|
|
db func(*testing.T) Queries
|
|
config *Config
|
|
systemID string
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
reportID string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "database error, error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 1).Return(
|
|
nil, zerrors.ThrowInternal(nil, "id", "db error"),
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
return mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 1,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
reportID: "",
|
|
},
|
|
wantErr: zerrors.ThrowInternal(nil, "id", "db error"),
|
|
},
|
|
{
|
|
name: "no resource counts, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 1).Return(
|
|
[]query.ResourceCount{}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
return mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 1,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
reportID: "",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "telemetry client error, error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: nil,
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
},
|
|
}).Return(
|
|
nil, status.Error(codes.Internal, "error"),
|
|
)
|
|
return client
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
reportID: "",
|
|
},
|
|
wantErr: status.Error(codes.Internal, "error"),
|
|
},
|
|
{
|
|
name: "report ok, no additional counts, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: nil,
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportResourceCountsResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
reportID: "",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "report ok, additional counts, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
{
|
|
ID: 2,
|
|
InstanceID: "instance-id2",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id2",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 5,
|
|
},
|
|
}, nil,
|
|
)
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 2, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 3,
|
|
InstanceID: "instance-id3",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id3",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 20,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: nil,
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
{
|
|
InstanceId: "instance-id2",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id2",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 5,
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportResourceCountsResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: gu.Ptr("report-id"),
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id3",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id3",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 20,
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportResourceCountsResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
reportID: "",
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &Worker{
|
|
reportClient: tt.fields.reportClient(t),
|
|
db: tt.fields.db(t),
|
|
config: tt.fields.config,
|
|
systemID: tt.fields.systemID,
|
|
}
|
|
err := w.reportResourceCounts(tt.args.ctx, tt.args.reportID)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorker_Work(t *testing.T) {
|
|
type fields struct {
|
|
WorkerDefaults river.WorkerDefaults[*ServicePingReport]
|
|
reportClient func(*testing.T) analytics.TelemetryServiceClient
|
|
db func(*testing.T) Queries
|
|
queue func(*testing.T) Queue
|
|
config *Config
|
|
systemID string
|
|
version string
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
job *river.Job[*ServicePingReport]
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "unknown report type, cancel job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
return mock.NewMockQueries(gomock.NewController(t))
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
return mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: 100000,
|
|
},
|
|
},
|
|
},
|
|
wantErr: river.JobCancel(ErrInvalidReportType),
|
|
},
|
|
{
|
|
name: "report base information, database error, retry job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
nil, zerrors.ThrowInternal(nil, "id", "db error"),
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
return mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeBaseInformation,
|
|
},
|
|
},
|
|
},
|
|
wantErr: zerrors.ThrowInternal(nil, "id", "db error"),
|
|
},
|
|
{
|
|
name: "report base information, config error, cancel job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
nil, &TelemetryError{StatusCode: http.StatusNotFound, Body: []byte("endpoint not found")},
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeBaseInformation,
|
|
},
|
|
},
|
|
},
|
|
wantErr: river.JobCancel(&TelemetryError{StatusCode: http.StatusNotFound, Body: []byte("endpoint not found")}),
|
|
},
|
|
{
|
|
name: "report base information, no reports enabled, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportBaseInformationResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeBaseInformation,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "report base information, job creation error, cancel job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportBaseInformationResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
q := mock.NewMockQueue(gomock.NewController(t))
|
|
q.EXPECT().Insert(gomock.Any(),
|
|
&ServicePingReport{
|
|
ReportID: "report-id",
|
|
ReportType: ReportTypeResourceCounts,
|
|
},
|
|
gomock.AssignableToTypeOf(reflect.TypeOf(queue.WithQueueName(QueueName))),
|
|
gomock.AssignableToTypeOf(reflect.TypeOf(queue.WithMaxAttempts(5)))). // TODO: better solution
|
|
Return(errInsert)
|
|
return q
|
|
},
|
|
config: &Config{
|
|
MaxAttempts: 5,
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeBaseInformation,
|
|
},
|
|
},
|
|
},
|
|
wantErr: errInsert,
|
|
},
|
|
{
|
|
name: "report base information, success, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().SearchInstances(gomock.Any(), &query.InstanceSearchQueries{}).Return(
|
|
&query.Instances{
|
|
Instances: []*query.Instance{
|
|
{
|
|
ID: "id",
|
|
CreationDate: testNow,
|
|
Domains: []*query.InstanceDomain{
|
|
{
|
|
Domain: "domain",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportBaseInformation(gomock.Any(), &analytics.ReportBaseInformationRequest{
|
|
SystemId: "system-id",
|
|
Version: "version",
|
|
Instances: []*analytics.InstanceInformation{
|
|
{
|
|
Id: "id",
|
|
Domains: []string{"domain"},
|
|
CreatedAt: timestamppb.New(testNow),
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportBaseInformationResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
q := mock.NewMockQueue(gomock.NewController(t))
|
|
q.EXPECT().Insert(gomock.Any(),
|
|
&ServicePingReport{
|
|
ReportID: "report-id",
|
|
ReportType: ReportTypeResourceCounts,
|
|
},
|
|
gomock.AssignableToTypeOf(reflect.TypeOf(queue.WithQueueName(QueueName))),
|
|
gomock.AssignableToTypeOf(reflect.TypeOf(queue.WithMaxAttempts(5)))).
|
|
Return(nil)
|
|
return q
|
|
},
|
|
config: &Config{
|
|
MaxAttempts: 5,
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
version: "version",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeBaseInformation,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "report resource counts, service unavailable, retry job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: gu.Ptr("report-id"),
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
},
|
|
}).Return(
|
|
nil, status.Error(codes.Unavailable, "service unavailable"),
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportType: ReportTypeResourceCounts,
|
|
ReportID: "report-id",
|
|
},
|
|
},
|
|
},
|
|
wantErr: status.Error(codes.Unavailable, "service unavailable"),
|
|
},
|
|
{
|
|
name: "report resource counts, precondition error, cancel job",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: gu.Ptr("report-id"),
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
},
|
|
}).Return(
|
|
nil, &TelemetryError{StatusCode: http.StatusPreconditionFailed, Body: []byte("report too old")},
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportID: "report-id",
|
|
ReportType: ReportTypeResourceCounts,
|
|
},
|
|
},
|
|
},
|
|
wantErr: river.JobCancel(&TelemetryError{StatusCode: http.StatusPreconditionFailed, Body: []byte("report too old")}),
|
|
},
|
|
{
|
|
name: "report resource counts, success, no error",
|
|
fields: fields{
|
|
db: func(t *testing.T) Queries {
|
|
queries := mock.NewMockQueries(gomock.NewController(t))
|
|
queries.EXPECT().ListResourceCounts(gomock.Any(), 0, 2).Return(
|
|
[]query.ResourceCount{
|
|
{
|
|
ID: 1,
|
|
InstanceID: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: domain.CountParentTypeInstance,
|
|
ParentID: "instance-id",
|
|
Resource: "resource",
|
|
UpdatedAt: testNow,
|
|
Amount: 10,
|
|
},
|
|
}, nil,
|
|
)
|
|
return queries
|
|
},
|
|
reportClient: func(t *testing.T) analytics.TelemetryServiceClient {
|
|
client := mock.NewMockTelemetryServiceClient(gomock.NewController(t))
|
|
client.EXPECT().ReportResourceCounts(gomock.Any(), &analytics.ReportResourceCountsRequest{
|
|
SystemId: "system-id",
|
|
ReportId: gu.Ptr("report-id"),
|
|
ResourceCounts: []*analytics.ResourceCount{
|
|
{
|
|
InstanceId: "instance-id",
|
|
TableName: "table_name",
|
|
ParentType: analytics.CountParentType_COUNT_PARENT_TYPE_INSTANCE,
|
|
ParentId: "instance-id",
|
|
ResourceName: "resource",
|
|
UpdatedAt: timestamppb.New(testNow),
|
|
Amount: 10,
|
|
},
|
|
},
|
|
}).Return(
|
|
&analytics.ReportResourceCountsResponse{
|
|
ReportId: "report-id",
|
|
}, nil,
|
|
)
|
|
return client
|
|
},
|
|
queue: func(t *testing.T) Queue {
|
|
return mock.NewMockQueue(gomock.NewController(t))
|
|
},
|
|
config: &Config{
|
|
Telemetry: TelemetryConfig{
|
|
ResourceCount: ResourceCount{
|
|
BulkSize: 2,
|
|
},
|
|
},
|
|
},
|
|
systemID: "system-id",
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
job: &river.Job[*ServicePingReport]{
|
|
Args: &ServicePingReport{
|
|
ReportID: "report-id",
|
|
ReportType: ReportTypeResourceCounts,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &Worker{
|
|
WorkerDefaults: river.WorkerDefaults[*ServicePingReport]{},
|
|
reportClient: tt.fields.reportClient(t),
|
|
db: tt.fields.db(t),
|
|
queue: tt.fields.queue(t),
|
|
config: tt.fields.config,
|
|
systemID: tt.fields.systemID,
|
|
version: tt.fields.version,
|
|
}
|
|
err := w.Work(tt.args.ctx, tt.args.job)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_parseAndValidateSchedule(t *testing.T) {
|
|
type args struct {
|
|
interval string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantNextStart time.Time
|
|
wantNextEnd time.Time
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "@daily, returns randomized daily schedule",
|
|
args: args{
|
|
interval: "@daily",
|
|
},
|
|
wantNextStart: time.Now(),
|
|
wantNextEnd: time.Now().Add(24 * time.Hour),
|
|
},
|
|
{
|
|
name: "invalid cron expression, returns error",
|
|
args: args{
|
|
interval: "invalid cron",
|
|
},
|
|
wantErr: zerrors.ThrowInvalidArgument(nil, "SERV-NJqiof", "invalid interval"),
|
|
},
|
|
{
|
|
name: "valid cron expression, returns schedule",
|
|
args: args{
|
|
interval: "0 0 * * *",
|
|
},
|
|
wantNextStart: nextMidnight(),
|
|
wantNextEnd: nextMidnight(),
|
|
},
|
|
{
|
|
name: "valid cron expression (extended syntax), returns schedule",
|
|
args: args{
|
|
interval: "@midnight",
|
|
},
|
|
wantNextStart: nextMidnight(),
|
|
wantNextEnd: nextMidnight(),
|
|
},
|
|
{
|
|
name: "less than minInterval, returns error",
|
|
args: args{
|
|
interval: "0/15 * * * *",
|
|
},
|
|
wantErr: zerrors.ThrowInvalidArgumentf(nil, "SERV-FJ12", "interval must be at least %s", minInterval),
|
|
},
|
|
{
|
|
name: "less than minInterval (extended syntax), returns error",
|
|
args: args{
|
|
interval: "@every 15m",
|
|
},
|
|
wantErr: zerrors.ThrowInvalidArgumentf(nil, "SERV-FJ12", "interval must be at least %s", minInterval),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := parseAndValidateSchedule(tt.args.interval)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
if tt.wantErr == nil {
|
|
now := time.Now()
|
|
assert.WithinRange(t, got.Next(now), tt.wantNextStart, tt.wantNextEnd)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func nextMidnight() time.Time {
|
|
year, month, day := time.Now().Date()
|
|
return time.Date(year, month, day+1, 0, 0, 0, 0, time.Local)
|
|
}
|