package api import ( "context" "encoding/json" "errors" "log/slog" "os" "testing" "time" "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/audit/messaging" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/common-audit.git/gen/go/audit/v1" "github.com/Azure/go-amqp" "github.com/bufbuild/protovalidate-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestLegacyAuditApi(t *testing.T) { slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) // Specify test timeout ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second) defer cancelFn() // Start solace docker container solaceContainer, err := messaging.NewSolaceContainer(context.Background()) assert.NoError(t, err) defer solaceContainer.Stop() // Instantiate the messaging api messagingApi, err := messaging.NewAmqpMessagingApi(messaging.AmqpConfig{URL: solaceContainer.AmqpConnectionString}) assert.NoError(t, err) // Validator validator, err := protovalidate.New() assert.NoError(t, err) topicSubscriptionTopicPattern := "audit-log/>" // Check logging of organization events t.Run("Log public organization event", func(t *testing.T) { defer solaceContainer.StopOnError() slog.Info("test abc") // Create the queue and topic subscription in solace queueName := "org-event-public-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PUBLIC assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) t.Run("Log private organization event", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "org-event-private-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PRIVATE assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) // Check logging of folder events t.Run("Log public folder event", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "folder-event-public-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewFolderAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PUBLIC assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) t.Run("Log private folder event", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "folder-event-private-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/folder-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewFolderAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PRIVATE assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) // Check logging of project events t.Run("Log public project event", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "project-event-public-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewProjectAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PUBLIC assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) t.Run("Log private project event", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "project-event-private-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/project-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewProjectAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PRIVATE assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessage(t, topicName, message, event) }) // Check logging of system events t.Run("Log private system event", func(t *testing.T) { defer solaceContainer.StopOnError() queueName := "system-event-private" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/system-changed" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event := NewSystemAuditEvent(nil) // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PRIVATE assert.NoError(t, (*auditApi).Log( ctx, event, visibility, nil, nil, )) // Receive the event from solace message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) // Check topic name assert.Equal(t, topicName, *message.Properties.To) // Check topic name assert.Equal(t, topicName, *message.Properties.To) // Check deserialized message var auditEvent LegacyAuditEvent assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.Equal(t, event.EventName, auditEvent.EventName) assert.Equal(t, event.EventTimeStamp.AsTime(), auditEvent.EventTimeStamp) assert.Equal(t, event.Initiator.Id, auditEvent.Initiator.Id) assert.Equal(t, "SYSTEM_EVENT", auditEvent.EventType) assert.Equal(t, "INFO", auditEvent.Severity) assert.Equal(t, "none", auditEvent.Request.Endpoint) assert.Equal(t, "0.0.0.0", auditEvent.SourceIpAddress) assert.Equal(t, "none", auditEvent.UserAgent) }) t.Run("Log event with details", func(t *testing.T) { defer solaceContainer.StopOnError() // Create the queue and topic subscription in solace queueName := "org-event-with-details-legacy" assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName)) assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, topicSubscriptionTopicPattern)) topicName := "topic://audit-log/eu01/v1/resource-manager/organization-created" assert.NoError(t, solaceContainer.ValidateTopicName(topicSubscriptionTopicPattern, topicName)) // Instantiate audit api auditApi, err := NewLegacyAuditApi( messagingApi, LegacyTopicNameConfig{TopicName: topicName}, validator, ) assert.NoError(t, err) // Instantiate test data event, routingIdentifier, objectIdentifier := NewOrganizationAuditEventWithDetails() // Log the event to solace visibility := auditV1.Visibility_VISIBILITY_PUBLIC assert.NoError(t, (*auditApi).Log( ctx, event, visibility, routingIdentifier, objectIdentifier, )) message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true) assert.NoError(t, err) validateSentMessageWithDetails(t, topicName, message, event) }) } func validateSentMessage(t *testing.T, topicName string, message *amqp.Message, event *auditV1.AuditEvent) { // Check topic name assert.Equal(t, topicName, *message.Properties.To) // Check deserialized message var auditEvent LegacyAuditEvent assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.Equal(t, event.EventName, auditEvent.EventName) assert.Equal(t, event.EventTimeStamp.AsTime(), auditEvent.EventTimeStamp) assert.Equal(t, event.Initiator.Id, auditEvent.Initiator.Id) assert.Equal(t, "ADMIN_ACTIVITY", auditEvent.EventType) assert.Equal(t, "INFO", auditEvent.Severity) assert.Equal(t, "none", auditEvent.Request.Endpoint) assert.Equal(t, "0.0.0.0", auditEvent.SourceIpAddress) assert.Equal(t, "none", auditEvent.UserAgent) } func validateSentMessageWithDetails(t *testing.T, topicName string, message *amqp.Message, event *auditV1.AuditEvent) { // Check topic name assert.Equal(t, topicName, *message.Properties.To) // Check deserialized message var auditEvent LegacyAuditEvent assert.NoError(t, json.Unmarshal(message.Data[0], &auditEvent)) assert.Equal(t, event.EventName, auditEvent.EventName) assert.Equal(t, event.EventTimeStamp.AsTime(), auditEvent.EventTimeStamp) assert.Equal(t, event.Initiator.Id, auditEvent.Initiator.Id) assert.Equal(t, "ADMIN_ACTIVITY", auditEvent.EventType) assert.Equal(t, "INFO", auditEvent.Severity) assert.Equal(t, event.Request.Endpoint, auditEvent.Request.Endpoint) assert.Equal(t, event.Request.SourceIpAddress, auditEvent.SourceIpAddress) assert.Equal(t, *event.Request.UserAgent, auditEvent.UserAgent) assert.Equal(t, event.Request.Body.AsMap(), *auditEvent.Request.Body) assert.Equal(t, event.Request.Parameters.AsMap(), *auditEvent.Request.Parameters) assert.Equal(t, event.Details.AsMap(), *auditEvent.Details) assert.Equal(t, event.Result.AsMap(), *auditEvent.Result) for _, header := range event.Request.Headers { assert.Equal(t, header.Value, (*auditEvent.Request.Headers)[header.Key]) } for idx := range event.Principals { assert.Equal(t, event.Principals[idx].Id, auditEvent.ServiceAccountDelegationInfo.Principals[idx].Id) assert.Equal(t, event.Principals[idx].Email, auditEvent.ServiceAccountDelegationInfo.Principals[idx].Email) } } func TestLegacyAuditApi_NewLegacyAuditApi_MessagingApiNil(t *testing.T) { auditApi, err := NewLegacyAuditApi(nil, LegacyTopicNameConfig{}, nil) assert.Nil(t, auditApi) assert.EqualError(t, err, "messaging api nil") } func TestLegacyAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) { expectedError := errors.New("expected error") validator := &ProtobufValidatorMock{} validator.On("Validate", mock.Anything).Return(expectedError) var protobufValidator ProtobufValidator = validator auditApi := LegacyAuditApi{validator: &protobufValidator} event := NewSystemAuditEvent(nil) _, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil) assert.ErrorIs(t, err, expectedError) } func TestLegacyAuditApi_Log_ValidationFailed(t *testing.T) { expectedError := errors.New("expected error") validator := &ProtobufValidatorMock{} validator.On("Validate", mock.Anything).Return(expectedError) var protobufValidator ProtobufValidator = validator auditApi := LegacyAuditApi{validator: &protobufValidator} event := NewSystemAuditEvent(nil) err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil) assert.ErrorIs(t, err, expectedError) } func TestLegacyAuditApi_Log_NilEvent(t *testing.T) { auditApi := LegacyAuditApi{} err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil) assert.ErrorIs(t, err, ErrEventNil) } func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormatInvalidObjectIdentifierType(t *testing.T) { customization := func(event *auditV1.AuditEvent, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier) { objectIdentifier.Type = 4 } event, identifier, objectIdentifier := NewProjectAuditEvent(&customization) validator := &ProtobufValidatorMock{} validator.On("Validate", mock.Anything).Return(nil) var protobufValidator ProtobufValidator = validator auditApi := LegacyAuditApi{validator: &protobufValidator} _, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, identifier, objectIdentifier) assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType) } func TestLegacyAuditApi_ConvertAndSerializeIntoLegacyFormat_NoResourceReference(t *testing.T) { event, _, _ := NewProjectAuditEvent(nil) routableEvent := auditV1.RoutableAuditEvent{ EventName: event.EventName, Visibility: auditV1.Visibility_VISIBILITY_PUBLIC, ResourceReference: nil, Data: nil, } auditApi := LegacyAuditApi{} _, err := auditApi.convertAndSerializeIntoLegacyFormat(event, &routableEvent) assert.ErrorIs(t, err, ErrUnsupportedResourceReferenceType) }