Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions tests/OpenTelemetry/AuditEventTypesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,36 @@
namespace Tests\OpenTelemetry;

use App\Audit\AuditLogOtlpStrategy;
use App\Audit\AuditContext;
use OpenTelemetry\API\Trace\StatusCode;

class AuditEventTypesTest extends OpenTelemetryTestCase
{
private const MOCK_ENTITY_ID = 999;
private const MOCK_ENTITY_TITLE = 'Test Entity';
private const NEW_ENTITY_TITLE = 'New Entity';
private const TEST_TYPE = 'test';
private const OLD_TITLE = 'Old Title';
private const NEW_TITLE = 'New Title';
private const DELETED_ID = 999;
private const CLEANUP_REASON = 'Test cleanup';
private const USER_ID = 999;
private const USER_EMAIL = 'test@example.com';
private const USER_FIRST_NAME = 'Test';
private const USER_LAST_NAME = 'User';
private const TEST_APP = 'test-app';
private const TEST_FLOW = 'test-flow';
private const TEST_ROUTE = 'api.test';
private const TEST_HTTP_METHOD = 'POST';
private const TEST_CLIENT_IP = '127.0.0.1';
private const TEST_USER_AGENT = 'Test-Agent/1.0';
private const SPAN_CREATION = 'test.audit.creation';
private const SPAN_UPDATE = 'test.audit.update';
private const SPAN_DELETION = 'test.audit.deletion';
private const CREATION_COMPLETED = 'Creation audit completed';
private const UPDATE_COMPLETED = 'Update audit completed';
private const DELETION_COMPLETED = 'Deletion audit completed';

private AuditLogOtlpStrategy $auditStrategy;

protected function setUp(): void
Expand All @@ -23,20 +49,22 @@ public function testEntityCreationAudit(): void
}

$tracer = $this->app->make(\OpenTelemetry\API\Trace\TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.creation')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_CREATION)->startSpan();
$spanScope = $span->activate();

try {
$mockEntity = (object) ['id' => 999, 'title' => 'Test Entity'];
$data = ['title' => 'New Entity', 'type' => 'test'];
$mockEntity = (object) ['id' => self::MOCK_ENTITY_ID, 'title' => self::MOCK_ENTITY_TITLE];
$data = ['title' => self::NEW_ENTITY_TITLE, 'type' => self::TEST_TYPE];
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
$mockEntity,
$data,
AuditLogOtlpStrategy::EVENT_ENTITY_CREATION
AuditLogOtlpStrategy::EVENT_ENTITY_CREATION,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'Creation audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::CREATION_COMPLETED);
$this->assertTrue(true);
} catch (\Exception $e) {
$span->recordException($e);
Expand All @@ -54,20 +82,22 @@ public function testEntityUpdateAudit(): void
}

$tracer = $this->app->make(\OpenTelemetry\API\Trace\TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.update')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_UPDATE)->startSpan();
$spanScope = $span->activate();

try {
$mockEntity = (object) ['id' => 999, 'title' => 'Test Entity'];
$data = ['title' => ['Old Title', 'New Title']];
$mockEntity = (object) ['id' => self::MOCK_ENTITY_ID, 'title' => self::MOCK_ENTITY_TITLE];
$data = ['title' => [self::OLD_TITLE, self::NEW_TITLE]];
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
$mockEntity,
$data,
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'Update audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::UPDATE_COMPLETED);
$this->assertTrue(true);
} catch (\Exception $e) {
$span->recordException($e);
Expand All @@ -85,20 +115,22 @@ public function testEntityDeletionAudit(): void
}

$tracer = $this->app->make(\OpenTelemetry\API\Trace\TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.deletion')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_DELETION)->startSpan();
$spanScope = $span->activate();

try {
$mockEntity = (object) ['id' => 999, 'title' => 'Test Entity'];
$data = ['deleted_id' => 999, 'reason' => 'Test cleanup'];
$mockEntity = (object) ['id' => self::MOCK_ENTITY_ID, 'title' => self::MOCK_ENTITY_TITLE];
$data = ['deleted_id' => self::DELETED_ID, 'reason' => self::CLEANUP_REASON];
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
$mockEntity,
$data,
AuditLogOtlpStrategy::EVENT_ENTITY_DELETION
AuditLogOtlpStrategy::EVENT_ENTITY_DELETION,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'Deletion audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::DELETION_COMPLETED);
$this->assertTrue(true);
} catch (\Exception $e) {
$span->recordException($e);
Expand All @@ -116,4 +148,21 @@ private function isOpenTelemetryEnabled(): bool
{
return getenv('OTEL_SERVICE_ENABLED') === 'true';
}

private function createAuditContext(): AuditContext
{
return new AuditContext(
userId: self::USER_ID,
userEmail: self::USER_EMAIL,
userFirstName: self::USER_FIRST_NAME,
userLastName: self::USER_LAST_NAME,
uiApp: self::TEST_APP,
uiFlow: self::TEST_FLOW,
route: self::TEST_ROUTE,
rawRoute: self::TEST_ROUTE,
httpMethod: self::TEST_HTTP_METHOD,
clientIp: self::TEST_CLIENT_IP,
userAgent: self::TEST_USER_AGENT,
);
}
}
85 changes: 57 additions & 28 deletions tests/OpenTelemetry/AuditOtlpStrategyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ class AuditOtlpStrategyTest extends OpenTelemetryTestCase
use InsertSummitTestData;
use InsertMemberTestData;

private const TEST_APP = 'test-app';
private const TEST_FLOW = 'test-flow';
private const TEST_ROUTE = 'api.summits.update';
private const TEST_HTTP_METHOD = 'PUT';
private const TEST_CLIENT_IP = '127.0.0.1';
private const TEST_USER_AGENT = 'Test-Agent/1.0';
private const SPAN_SUMMIT_CHANGE = 'test.audit.summit_change';
private const SPAN_SUMMIT_EVENT_CHANGE = 'test.audit.summit_event_change';
private const SPAN_NO_ACTIVE_SPAN = 'test.audit.no_span';
private const SPAN_EMPTY_CHANGESET = 'test.audit.empty_changeset';
private const SUFFIX_TEST = '[TEST]';
private const SUFFIX_UPDATED = '[UPDATED]';
private const SPAN_EVENT_STARTED = 'test.started';
private const EVENT_COMPLETED = 'Summit audit completed';
private const EVENT_CHANGE_COMPLETED = 'SummitEvent audit completed';
private const EVENT_EMPTY_COMPLETED = 'Empty changeset audit completed';

private AuditLogOtlpStrategy $auditStrategy;

protected function setUp(): void
Expand All @@ -51,24 +68,26 @@ public function testAuditSummitChangeWithOtlp(): void
$this->skipIfOpenTelemetryDisabled();

$tracer = $this->app->make(TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.summit_change')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_SUMMIT_CHANGE)->startSpan();
$spanScope = $span->activate();

try {
$span->addEvent('test.started', [
$span->addEvent(self::SPAN_EVENT_STARTED, [
'summit_id' => self::$summit->getId(),
'summit_name' => self::$summit->getName()
]);

$simulatedChangeSet = $this->createSummitChangeSet();
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
self::$summit,
$simulatedChangeSet,
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'Summit audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::EVENT_COMPLETED);
$this->assertTrue(true);

} catch (\Exception $e) {
Expand All @@ -86,20 +105,22 @@ public function testAuditSummitEventChangeWithOtlp(): void
$this->skipIfOpenTelemetryDisabled();

$tracer = $this->app->make(TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.summit_event_change')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_SUMMIT_EVENT_CHANGE)->startSpan();
$spanScope = $span->activate();

try {
$summitEvent = self::$summit->getEvents()[0];
$simulatedChangeSet = $this->createSummitEventChangeSet($summitEvent);
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
$summitEvent,
$simulatedChangeSet,
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'SummitEvent audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::EVENT_CHANGE_COMPLETED);
$this->assertTrue(true);

} catch (\Exception $e) {
Expand All @@ -117,11 +138,13 @@ public function testAuditStrategyWithoutActiveSpan(): void
$this->skipIfOpenTelemetryDisabled();

$simulatedChangeSet = ['name' => ['Old Name', 'New Name']];
$ctx = $this->createAuditContext();

$this->auditStrategy->audit(
self::$summit,
$simulatedChangeSet,
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE,
$ctx
);

$this->assertTrue(true);
Expand All @@ -132,17 +155,19 @@ public function testAuditStrategyWithEmptyChangeSet(): void
$this->skipIfOpenTelemetryDisabled();

$tracer = $this->app->make(TracerInterface::class);
$span = $tracer->spanBuilder('test.audit.empty_changeset')->startSpan();
$span = $tracer->spanBuilder(self::SPAN_EMPTY_CHANGESET)->startSpan();
$spanScope = $span->activate();

try {
$ctx = $this->createAuditContext();
$this->auditStrategy->audit(
self::$summit,
[],
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE
AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE,
$ctx
);

$span->setStatus(StatusCode::STATUS_OK, 'Empty changeset audit completed');
$span->setStatus(StatusCode::STATUS_OK, self::EVENT_EMPTY_COMPLETED);
$this->assertTrue(true);

} catch (\Exception $e) {
Expand All @@ -165,15 +190,15 @@ private function skipIfOpenTelemetryDisabled(): void
private function createSummitChangeSet(): array
{
return [
'name' => [self::$summit->getName(), self::$summit->getName() . ' [TEST]'],
'name' => [self::$summit->getName(), self::$summit->getName() . self::SUFFIX_TEST],
'description' => ['Original', 'Updated for test']
];
}

private function createSummitEventChangeSet(object $summitEvent): array
{
return [
'title' => [$summitEvent->getTitle(), $summitEvent->getTitle() . ' [TEST]']
'title' => [$summitEvent->getTitle(), $summitEvent->getTitle() . self::SUFFIX_TEST]
];
}

Expand All @@ -182,27 +207,33 @@ private function isOpenTelemetryEnabled(): bool
return getenv('OTEL_SERVICE_ENABLED') === 'true';
}

private function createAuditContext(): AuditContext
{
return new AuditContext(
userId: self::$member->getId(),
userEmail: self::$member->getEmail(),
userFirstName: self::$member->getFirstName(),
userLastName: self::$member->getLastName(),
uiApp: self::TEST_APP,
uiFlow: self::TEST_FLOW,
route: self::TEST_ROUTE,
rawRoute: self::TEST_ROUTE,
httpMethod: self::TEST_HTTP_METHOD,
clientIp: self::TEST_CLIENT_IP,
userAgent: self::TEST_USER_AGENT,
);
}


public function testAuditSummitEntityPopulatesSummitIdCorrectly(): void
{
$this->skipIfOpenTelemetryDisabled();

Queue::fake();

$ctx = new AuditContext();
$ctx->userId = self::$member->getId();
$ctx->userEmail = self::$member->getEmail();
$ctx->userFirstName = self::$member->getFirstName();
$ctx->userLastName = self::$member->getLastName();
$ctx->uiApp = 'test-app';
$ctx->uiFlow = 'test-flow';
$ctx->route = 'api.summits.update';
$ctx->httpMethod = 'PUT';
$ctx->clientIp = '127.0.0.1';
$ctx->userAgent = 'Test-Agent/1.0';

$ctx = $this->createAuditContext();
$simulatedChangeSet = [
'name' => [self::$summit->getName(), self::$summit->getName() . ' [UPDATED]']
'name' => [self::$summit->getName(), self::$summit->getName() . self::SUFFIX_UPDATED]
];

$this->auditStrategy->audit(
Expand All @@ -215,9 +246,7 @@ public function testAuditSummitEntityPopulatesSummitIdCorrectly(): void
Queue::assertPushed(EmitAuditLogJob::class, function ($job) {
$this->assertArrayHasKey('audit.summit_id', $job->auditData);
$this->assertEquals((string)self::$summit->getId(), $job->auditData['audit.summit_id']);

$this->assertEquals('Summit', $job->auditData['audit.entity']);

return true;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class SummitAttendeeBadgeAuditLogFormatterTest extends TestCase
private const TICKET_ID = 789;
private const TICKET_NUMBER = 'TICKET-2024-001';
private const SUMMIT_NAME = 'OpenStack Summit 2024';
private const OWNER_FIRST_NAME = 'John';
private const OWNER_LAST_NAME = 'Doe';

private mixed $mockSubject;

Expand All @@ -37,10 +39,15 @@ private function createMockSubject(): mixed
$mockOrder = Mockery::mock('models\summit\SummitOrder');
$mockOrder->shouldReceive('getSummit')->andReturn($mockSummit);

$mockOwner = Mockery::mock('models\summit\SummitAttendee');
$mockOwner->shouldReceive('getFirstName')->andReturn(self::OWNER_FIRST_NAME);
$mockOwner->shouldReceive('getSurname')->andReturn(self::OWNER_LAST_NAME);

$mockTicket = Mockery::mock('models\summit\SummitAttendeeTicket');
$mockTicket->shouldReceive('getId')->andReturn(self::TICKET_ID);
$mockTicket->shouldReceive('getNumber')->andReturn(self::TICKET_NUMBER);
$mockTicket->shouldReceive('getOrder')->andReturn($mockOrder);
$mockTicket->shouldReceive('getOwner')->andReturn($mockOwner);

$mock = Mockery::mock('models\summit\SummitAttendeeBadge');
$mock->shouldReceive('getId')->andReturn(self::BADGE_ID);
Expand All @@ -58,8 +65,8 @@ public function testSubjectCreationAuditMessage(): void

$this->assertNotNull($result);
$this->assertStringContainsString('created', $result);
$this->assertStringContainsString(self::TICKET_NUMBER, $result);
$this->assertStringContainsString((string)self::TICKET_ID, $result);
$this->assertStringContainsString(self::OWNER_FIRST_NAME . ' ' . self::OWNER_LAST_NAME, $result);
$this->assertStringContainsString((string)self::BADGE_ID, $result);
}

public function testSubjectUpdateAuditMessage(): void
Expand Down
Loading