audit-go/api_routable_test.go
2024-07-05 10:38:18 +02:00

396 lines
13 KiB
Go

package main
import (
auditV1 "audit-schema/gen/go/audit/v1"
"context"
"errors"
"fmt"
"github.com/Azure/go-amqp"
"github.com/bufbuild/protovalidate-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
func TestRoutableAuditApi(t *testing.T) {
// Specify test timeout
ctx, cancelFn := context.WithTimeout(context.Background(), 120*time.Second)
defer cancelFn()
// Start solace docker container
solaceContainer, err := NewSolaceContainer(context.Background())
assert.NoError(t, err)
defer solaceContainer.Stop()
// Instantiate the messaging api
messagingApi, err := NewAmqpMessagingApi(AmqpConfig{URL: solaceContainer.AmqpConnectionString})
assert.NoError(t, err)
// Validator
validator, err := protovalidate.New()
assert.NoError(t, err)
// Instantiate the audit api
organizationTopicPrefix := "org"
projectTopicPrefix := "project"
systemTopicName := "topic://system/admin-events"
auditApi, err := newRoutableAuditApi(
messagingApi,
topicNameConfig{OrganizationTopicPrefix: organizationTopicPrefix, ProjectTopicPrefix: projectTopicPrefix, SystemTopicName: systemTopicName},
validator,
)
assert.NoError(t, err)
// Check logging of organization events
t.Run("Log public organization event", func(t *testing.T) {
defer solaceContainer.StopOnError()
// Create the queue and topic subscription in solace
queueName := "org-event-public"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// 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)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
})
t.Run("Log private organization event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "org-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, routingIdentifier, objectIdentifier := NewOrganizationAuditEvent(nil)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, fmt.Sprintf("org/%s", routingIdentifier.Identifier)))
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
(*auditApi).Log(
ctx,
event,
visibility,
routingIdentifier,
objectIdentifier,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
})
// 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"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// 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)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "FOLDER_CREATED", visibility)
})
t.Run("Log private folder event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "folder-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// Instantiate test data
event, routingIdentifier, objectIdentifier := NewFolderAuditEvent(nil)
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, fmt.Sprintf("org/%s", routingIdentifier.Identifier)))
// Log the event to solace
visibility := auditV1.Visibility_VISIBILITY_PRIVATE
assert.NoError(t,
(*auditApi).Log(
ctx,
event,
visibility,
routingIdentifier,
objectIdentifier,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "FOLDER_CREATED", visibility)
})
// Check logging of project events
t.Run("Log public project event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-event-public"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// 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,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, projectTopicPrefix, message, routingIdentifier, objectIdentifier, event, "PROJECT_CREATED", visibility)
})
t.Run("Log private project event", func(t *testing.T) {
defer solaceContainer.StopOnError()
queueName := "project-event-private"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "project/*"))
// 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,
))
// Receive the event from solace
message, err := solaceContainer.NextMessageFromQueue(ctx, queueName, true)
assert.NoError(t, err)
validateSentEvent(t, projectTopicPrefix, message, routingIdentifier, objectIdentifier, event, "PROJECT_CREATED", visibility)
})
// 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, "system/*"))
// 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, systemTopicName, *message.Properties.To)
// Check deserialized message
var protobufMessage auditV1.ProtobufMessage
assert.NoError(t, proto.Unmarshal(message.Data[0], &protobufMessage))
assert.Equal(t, "audit.v1.RoutableAuditEvent", protobufMessage.ProtobufType)
validateProtobufMessagePayload(t, message.Data[0], nil, nil, event, "SYSTEM_CHANGED", visibility)
})
// Check logging of organization events
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"
assert.NoError(t, solaceContainer.QueueCreate(ctx, queueName))
assert.NoError(t, solaceContainer.TopicSubscriptionCreate(ctx, queueName, "org/*"))
// 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)
validateSentEvent(t, organizationTopicPrefix, message, routingIdentifier, objectIdentifier, event, "ORGANIZATION_CREATED", visibility)
})
}
func validateSentEvent(t *testing.T, topicPrefix string, message *amqp.Message, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier, event *auditV1.AuditEvent, eventName string, visibility auditV1.Visibility) {
// Check topic name
assert.Equal(t,
fmt.Sprintf("topic://%s/%s", topicPrefix, routingIdentifier.Identifier),
*message.Properties.To)
// Check deserialized message
var protobufMessage auditV1.ProtobufMessage
assert.NoError(t, proto.Unmarshal(message.Data[0], &protobufMessage))
assert.Equal(t, "audit.v1.RoutableAuditEvent", protobufMessage.ProtobufType)
validateProtobufMessagePayload(t, message.Data[0], routingIdentifier, objectIdentifier, event, eventName, visibility)
}
func validateProtobufMessagePayload(t *testing.T, payload []byte, routingIdentifier *RoutingIdentifier, objectIdentifier *auditV1.ObjectIdentifier, event *auditV1.AuditEvent, eventName string, visibility auditV1.Visibility) {
// Check deserialized message
var protobufMessage auditV1.ProtobufMessage
assert.NoError(t, proto.Unmarshal(payload, &protobufMessage))
assert.Equal(t, "audit.v1.RoutableAuditEvent", protobufMessage.ProtobufType)
// Check routable audit event parameters
var routableAuditEvent auditV1.RoutableAuditEvent
assert.NoError(t, proto.Unmarshal(protobufMessage.Value, &routableAuditEvent))
assert.Equal(t, eventName, routableAuditEvent.EventName)
assert.Equal(t, visibility, routableAuditEvent.Visibility)
switch reference := routableAuditEvent.ResourceReference.(type) {
case *auditV1.RoutableAuditEvent_ObjectIdentifier:
assert.True(t, proto.Equal(objectIdentifier, reference.ObjectIdentifier))
switch routingIdentifier.Type {
case RoutingIdentifierTypeOrganization:
if objectIdentifier.Type == auditV1.ObjectType_OBJECT_TYPE_ORGANIZATION {
assert.Equal(t, routingIdentifier.Identifier.String(), reference.ObjectIdentifier.Identifier)
}
break
case RoutingIdentifierTypeProject:
assert.Equal(t, routingIdentifier.Identifier.String(), reference.ObjectIdentifier.Identifier)
assert.Equal(t, auditV1.ObjectType_OBJECT_TYPE_PROJECT, reference.ObjectIdentifier.Type)
break
default:
assert.Fail(t, "Routing identifier type not expected")
}
break
case *auditV1.RoutableAuditEvent_ObjectName:
assert.Nil(t, objectIdentifier)
assert.Nil(t, routingIdentifier)
assert.Equal(t, auditV1.ObjectName_OBJECT_NAME_SYSTEM, reference.ObjectName)
default:
assert.Fail(t, "Object name not expected")
}
var auditEvent auditV1.AuditEvent
switch data := routableAuditEvent.Data.(type) {
case *auditV1.RoutableAuditEvent_UnencryptedData:
assert.NoError(t, proto.Unmarshal(data.UnencryptedData.Data, &auditEvent))
break
default:
assert.Fail(t, "Encrypted data not expected")
}
// Check audit event
assert.True(t, proto.Equal(event, &auditEvent))
}
func TestRoutableTopicNameResolver_Resolve_UnsupportedIdentifierType(t *testing.T) {
resolver := routableTopicNameResolver{}
_, err := resolver.Resolve(&RoutingIdentifier{Type: 2})
assert.ErrorIs(t, err, ErrUnsupportedObjectIdentifierType)
}
func TestNewRoutableAuditApi_NewRoutableAuditApi_MessagingApiNil(t *testing.T) {
auditApi, err := newRoutableAuditApi(nil, topicNameConfig{}, nil)
assert.Nil(t, auditApi)
assert.EqualError(t, err, "messaging api nil")
}
func TestRoutableAuditApi_ValidateAndSerialize_ValidationFailed(t *testing.T) {
expectedError := errors.New("expected error")
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
auditApi := routableAuditApi{validator: &protobufValidator}
event := NewSystemAuditEvent(nil)
_, err := auditApi.ValidateAndSerialize(event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
assert.ErrorIs(t, err, expectedError)
}
func TestRoutableAuditApi_Log_ValidationFailed(t *testing.T) {
expectedError := errors.New("expected error")
validator := &ProtobufValidatorMock{}
validator.On("Validate", mock.Anything).Return(expectedError)
var protobufValidator ProtobufValidator = validator
auditApi := routableAuditApi{validator: &protobufValidator}
event := NewSystemAuditEvent(nil)
err := auditApi.Log(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
assert.ErrorIs(t, err, expectedError)
}
func TestRoutableAuditApi_Log_NilEvent(t *testing.T) {
auditApi := routableAuditApi{}
err := auditApi.Log(context.Background(), nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil, nil)
assert.ErrorIs(t, err, ErrEventNil)
}