mirror of
https://dev.azure.com/schwarzit/schwarzit.stackit-public/_git/audit-go
synced 2026-02-08 09:07:26 +00:00
414 lines
15 KiB
Go
414 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/audit/messaging"
|
|
auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-core-platform/audit-go.git/gen/go/audit/v1"
|
|
|
|
"github.com/bufbuild/protovalidate-go"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
type MessagingApiMock struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MessagingApiMock) Send(
|
|
ctx context.Context,
|
|
topic string,
|
|
data []byte,
|
|
contentType string,
|
|
applicationProperties map[string]any,
|
|
) error {
|
|
|
|
args := m.Called(ctx, topic, data, contentType, applicationProperties)
|
|
return args.Error(0)
|
|
}
|
|
|
|
type ProtobufValidatorMock struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *ProtobufValidatorMock) Validate(msg proto.Message) error {
|
|
args := m.Called(msg)
|
|
return args.Error(0)
|
|
}
|
|
|
|
type TopicNameResolverMock struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *TopicNameResolverMock) Resolve(routableIdentifier *RoutableIdentifier) (string, error) {
|
|
args := m.Called(routableIdentifier)
|
|
return args.String(0), args.Error(1)
|
|
}
|
|
|
|
func NewValidator(t *testing.T) ProtobufValidator {
|
|
validator, err := protovalidate.New()
|
|
var protoValidator ProtobufValidator = validator
|
|
assert.NoError(t, err)
|
|
|
|
return protoValidator
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_EventNil(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
_, err := validateAndSerializePartially(
|
|
&validator, nil, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
|
|
|
|
assert.ErrorIs(t, err, ErrEventNil)
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_AuditEventValidationFailed(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewOrganizationAuditEvent(nil)
|
|
event.LogName = ""
|
|
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.EqualError(t, err, "validation error:\n - log_name: value is required [required]")
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_RoutableEventValidationFailed(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewOrganizationAuditEvent(nil)
|
|
_, err := validateAndSerializePartially(&validator, event, 3, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.EqualError(t, err, "validation error:\n - visibility: value must be one of the defined enum values [enum.defined_only]")
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_CheckVisibility_Event(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewOrganizationAuditEvent(nil)
|
|
|
|
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
})
|
|
|
|
t.Run("Visibility public - object identifier system", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
|
|
})
|
|
|
|
t.Run("Visibility public - object identifier set", func(t *testing.T) {
|
|
routableEvent, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, routableEvent)
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier system", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier set", func(t *testing.T) {
|
|
routableEvent, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, routableEvent)
|
|
})
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_CheckVisibility_SystemEvent(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event := NewSystemAuditEvent(nil)
|
|
|
|
t.Run("Visibility public - object identifier nil", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, nil)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier nil", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, nil)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
})
|
|
|
|
t.Run("Visibility public - object identifier system", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, RoutableSystemIdentifier)
|
|
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierVisibilityMismatch)
|
|
})
|
|
|
|
t.Run("Visibility public - object identifier set", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(
|
|
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(SingularTypeOrganization)}))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier system", func(t *testing.T) {
|
|
routableEvent, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, routableEvent)
|
|
|
|
})
|
|
|
|
t.Run("Visibility private - object identifier set", func(t *testing.T) {
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, NewRoutableIdentifier(
|
|
&auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: string(SingularTypeOrganization)}))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
|
|
})
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_UnsupportedIdentifierType(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewFolderAuditEvent(nil)
|
|
objectIdentifier.Type = "invalid"
|
|
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_LogNameIdentifierMismatch(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewFolderAuditEvent(nil)
|
|
parts := strings.Split(event.LogName, "/")
|
|
identifier := parts[1]
|
|
|
|
t.Run("LogName type mismatch", func(t *testing.T) {
|
|
event.LogName = fmt.Sprintf("projects/%s/logs/admin-activity", identifier)
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeTypeInvalid)
|
|
})
|
|
|
|
t.Run("LogName identifier mismatch", func(t *testing.T) {
|
|
event.LogName = fmt.Sprintf("folders/%s/logs/admin-activity", uuid.NewString())
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
|
|
})
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_ResourceNameIdentifierMismatch(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event, objectIdentifier := NewFolderAuditEvent(nil)
|
|
parts := strings.Split(event.ProtoPayload.ResourceName, "/")
|
|
identifier := parts[1]
|
|
|
|
t.Run("ResourceName type mismatch", func(t *testing.T) {
|
|
event.ProtoPayload.ResourceName = fmt.Sprintf("projects/%s", identifier)
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeTypeInvalid)
|
|
})
|
|
|
|
t.Run("ResourceName identifier mismatch", func(t *testing.T) {
|
|
event.ProtoPayload.ResourceName = fmt.Sprintf("folders/%s", uuid.NewString())
|
|
_, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, NewRoutableIdentifier(objectIdentifier))
|
|
|
|
assert.ErrorIs(t, err, ErrAttributeIdentifierInvalid)
|
|
})
|
|
}
|
|
|
|
func Test_ValidateAndSerializePartially_SystemEvent(t *testing.T) {
|
|
validator := NewValidator(t)
|
|
|
|
event := NewSystemAuditEvent(nil)
|
|
|
|
routableEvent, err := validateAndSerializePartially(
|
|
&validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, RoutableSystemIdentifier)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, event.LogName, fmt.Sprintf("system/%s/logs/%s", SystemIdentifier.Identifier, EventTypeSystemEvent))
|
|
assert.True(t, proto.Equal(routableEvent.ObjectIdentifier, SystemIdentifier))
|
|
}
|
|
|
|
func Test_Send_TopicNameResolverNil(t *testing.T) {
|
|
err := send(nil, nil, context.Background(), nil, nil)
|
|
assert.ErrorIs(t, err, ErrTopicNameResolverNil)
|
|
}
|
|
|
|
func Test_Send_TopicNameResolutionError(t *testing.T) {
|
|
expectedError := errors.New("expected error")
|
|
|
|
topicNameResolverMock := TopicNameResolverMock{}
|
|
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", expectedError)
|
|
var topicNameResolver TopicNameResolver = &topicNameResolverMock
|
|
|
|
var cloudEvent = CloudEvent{}
|
|
|
|
var messagingApi messaging.Api = &messaging.AmqpApi{}
|
|
err := send(&topicNameResolver, &messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent)
|
|
assert.ErrorIs(t, err, expectedError)
|
|
}
|
|
|
|
func Test_Send_MessagingApiNil(t *testing.T) {
|
|
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
|
|
err := send(&topicNameResolver, nil, context.Background(), nil, nil)
|
|
assert.ErrorIs(t, err, ErrMessagingApiNil)
|
|
}
|
|
|
|
func Test_Send_CloudEventNil(t *testing.T) {
|
|
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
|
|
var messagingApi messaging.Api = &messaging.AmqpApi{}
|
|
|
|
err := send(&topicNameResolver, &messagingApi, context.Background(), nil, nil)
|
|
assert.ErrorIs(t, err, ErrCloudEventNil)
|
|
}
|
|
|
|
func Test_Send_ObjectIdentifierNil(t *testing.T) {
|
|
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
|
|
var messagingApi messaging.Api = &messaging.AmqpApi{}
|
|
var cloudEvent = CloudEvent{}
|
|
|
|
err := send(&topicNameResolver, &messagingApi, context.Background(), nil, &cloudEvent)
|
|
assert.ErrorIs(t, err, ErrObjectIdentifierNil)
|
|
}
|
|
|
|
func Test_Send_UnsupportedObjectIdentifierType(t *testing.T) {
|
|
var topicNameResolver TopicNameResolver = &LegacyTopicNameResolver{topicName: "test"}
|
|
var messagingApi messaging.Api = &messaging.AmqpApi{}
|
|
var cloudEvent = CloudEvent{}
|
|
var objectIdentifier = auditV1.ObjectIdentifier{Identifier: uuid.NewString(), Type: "unsupported"}
|
|
|
|
err := send(&topicNameResolver, &messagingApi, context.Background(), NewRoutableIdentifier(&objectIdentifier), &cloudEvent)
|
|
assert.ErrorIs(t, err, ErrUnsupportedRoutableType)
|
|
}
|
|
|
|
func Test_Send(t *testing.T) {
|
|
topicNameResolverMock := TopicNameResolverMock{}
|
|
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
|
|
var topicNameResolver TopicNameResolver = &topicNameResolverMock
|
|
|
|
messagingApiMock := MessagingApiMock{}
|
|
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
var messagingApi messaging.Api = &messagingApiMock
|
|
|
|
var cloudEvent = CloudEvent{}
|
|
assert.NoError(t, send(&topicNameResolver, &messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
|
|
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
|
|
}
|
|
|
|
func Test_SendAllHeadersSet(t *testing.T) {
|
|
topicNameResolverMock := TopicNameResolverMock{}
|
|
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
|
|
var topicNameResolver TopicNameResolver = &topicNameResolverMock
|
|
|
|
messagingApiMock := MessagingApiMock{}
|
|
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
var messagingApi messaging.Api = &messagingApiMock
|
|
|
|
traceState := "rojo=00f067aa0ba902b7,congo=t61rcWkgMzE"
|
|
traceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
|
|
expectedTime := time.Now()
|
|
var cloudEvent = CloudEvent{
|
|
SpecVersion: "1.0",
|
|
Source: "resourcemanager",
|
|
Id: "id",
|
|
Time: expectedTime,
|
|
DataContentType: ContentTypeCloudEventsProtobuf,
|
|
DataType: "type",
|
|
Subject: "subject",
|
|
TraceParent: &traceParent,
|
|
TraceState: &traceState,
|
|
}
|
|
assert.NoError(t, send(&topicNameResolver, &messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
|
|
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
|
|
arguments := messagingApiMock.Calls[0].Arguments
|
|
topic := arguments.Get(1).(string)
|
|
assert.Equal(t, "topic", topic)
|
|
contentType := arguments.Get(3).(string)
|
|
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType)
|
|
applicationProperties := arguments.Get(4).(map[string]any)
|
|
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
|
|
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
|
|
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
|
|
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
|
|
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
|
|
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
|
|
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
|
|
assert.Equal(t, traceParent, applicationProperties["cloudEvents:traceparent"])
|
|
assert.Equal(t, traceState, applicationProperties["cloudEvents:tracestate"])
|
|
messagingApiMock.AssertExpectations(t)
|
|
}
|
|
|
|
func Test_SendWithoutOptionalHeadersSet(t *testing.T) {
|
|
topicNameResolverMock := TopicNameResolverMock{}
|
|
topicNameResolverMock.On("Resolve", mock.Anything).Return("topic", nil)
|
|
var topicNameResolver TopicNameResolver = &topicNameResolverMock
|
|
|
|
messagingApiMock := MessagingApiMock{}
|
|
messagingApiMock.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
var messagingApi messaging.Api = &messagingApiMock
|
|
|
|
expectedTime := time.Now()
|
|
var cloudEvent = CloudEvent{
|
|
SpecVersion: "1.0",
|
|
Source: "resourcemanager",
|
|
Id: "id",
|
|
Time: expectedTime,
|
|
DataContentType: ContentTypeCloudEventsProtobuf,
|
|
DataType: "type",
|
|
Subject: "subject",
|
|
}
|
|
assert.NoError(t, send(&topicNameResolver, &messagingApi, context.Background(), RoutableSystemIdentifier, &cloudEvent))
|
|
assert.True(t, messagingApiMock.AssertNumberOfCalls(t, "Send", 1))
|
|
arguments := messagingApiMock.Calls[0].Arguments
|
|
topic := arguments.Get(1).(string)
|
|
assert.Equal(t, "topic", topic)
|
|
contentType := arguments.Get(3).(string)
|
|
assert.Equal(t, ContentTypeCloudEventsProtobuf, contentType)
|
|
applicationProperties := arguments.Get(4).(map[string]any)
|
|
assert.Equal(t, "1.0", applicationProperties["cloudEvents:specversion"])
|
|
assert.Equal(t, "resourcemanager", applicationProperties["cloudEvents:source"])
|
|
assert.Equal(t, "id", applicationProperties["cloudEvents:id"])
|
|
assert.Equal(t, expectedTime.UnixMilli(), applicationProperties["cloudEvents:time"])
|
|
assert.Equal(t, ContentTypeCloudEventsProtobuf, applicationProperties["cloudEvents:datacontenttype"])
|
|
assert.Equal(t, "type", applicationProperties["cloudEvents:type"])
|
|
assert.Equal(t, "subject", applicationProperties["cloudEvents:subject"])
|
|
assert.Equal(t, nil, applicationProperties["cloudEvents:traceparent"])
|
|
assert.Equal(t, nil, applicationProperties["cloudEvents:tracestate"])
|
|
messagingApiMock.AssertExpectations(t)
|
|
}
|