- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
210 lines
7.1 KiB
PHP
210 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Domain\SmartLink\Entities\SmartLink;
|
|
use App\Domain\SmartLink\Enums\LinkStatus;
|
|
use App\Domain\SmartLink\Enums\LinkType;
|
|
use App\Domain\SmartLink\ValueObjects\LinkTitle;
|
|
use App\Domain\SmartLink\ValueObjects\ShortCode;
|
|
use App\Framework\DateTime\SystemClock;
|
|
|
|
describe('SmartLink Entity', function () {
|
|
beforeEach(function () {
|
|
$this->clock = new SystemClock();
|
|
$this->shortCode = ShortCode::fromString('abc123');
|
|
$this->title = LinkTitle::fromString('My Smart Link');
|
|
});
|
|
|
|
describe('creation', function () {
|
|
it('creates smart link with required fields', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
expect($link->shortCode->toString())->toBe('abc123');
|
|
expect($link->type)->toBe(LinkType::RELEASE);
|
|
expect($link->title->toString())->toBe('My Smart Link');
|
|
expect($link->status)->toBe(LinkStatus::DRAFT);
|
|
expect($link->userId)->toBeNull();
|
|
expect($link->coverImageUrl)->toBeNull();
|
|
});
|
|
|
|
it('creates smart link with user ID', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title,
|
|
userId: 'user123'
|
|
);
|
|
|
|
expect($link->userId)->toBe('user123');
|
|
expect($link->isOwnedBy('user123'))->toBeTrue();
|
|
});
|
|
|
|
it('creates smart link with cover image', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title,
|
|
coverImageUrl: 'https://example.com/cover.jpg'
|
|
);
|
|
|
|
expect($link->coverImageUrl)->toBe('https://example.com/cover.jpg');
|
|
});
|
|
|
|
it('starts in DRAFT status', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
expect($link->status)->toBe(LinkStatus::DRAFT);
|
|
expect($link->isActive())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('status transitions', function () {
|
|
it('transitions from DRAFT to ACTIVE', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
$activeLink = $link->withStatus(LinkStatus::ACTIVE);
|
|
|
|
expect($activeLink->status)->toBe(LinkStatus::ACTIVE);
|
|
expect($activeLink->isActive())->toBeTrue();
|
|
});
|
|
|
|
it('transitions from ACTIVE to PAUSED', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
)->withStatus(LinkStatus::ACTIVE);
|
|
|
|
$pausedLink = $link->withStatus(LinkStatus::PAUSED);
|
|
|
|
expect($pausedLink->status)->toBe(LinkStatus::PAUSED);
|
|
});
|
|
|
|
it('throws exception for invalid transition', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
// Cannot transition directly from DRAFT to PAUSED
|
|
expect(fn() => $link->withStatus(LinkStatus::PAUSED))
|
|
->toThrow(\DomainException::class);
|
|
});
|
|
|
|
it('throws exception for invalid transition from EXPIRED', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
)->withStatus(LinkStatus::ACTIVE)->withStatus(LinkStatus::EXPIRED);
|
|
|
|
// Cannot transition from EXPIRED to ACTIVE
|
|
expect(fn() => $link->withStatus(LinkStatus::ACTIVE))
|
|
->toThrow(\DomainException::class);
|
|
});
|
|
});
|
|
|
|
describe('title updates', function () {
|
|
it('updates title', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
$newTitle = LinkTitle::fromString('Updated Title');
|
|
$updatedLink = $link->withTitle($newTitle);
|
|
|
|
expect($updatedLink->title->toString())->toBe('Updated Title');
|
|
expect($link->title->toString())->toBe('My Smart Link'); // Original unchanged
|
|
});
|
|
});
|
|
|
|
describe('access control', function () {
|
|
it('identifies active links as active', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
)->withStatus(LinkStatus::ACTIVE);
|
|
|
|
expect($link->isActive())->toBeTrue();
|
|
expect($link->canBeAccessed())->toBeTrue();
|
|
});
|
|
|
|
it('identifies draft links as not accessible', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
expect($link->canBeAccessed())->toBeFalse();
|
|
});
|
|
|
|
it('identifies paused links as not accessible', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
)->withStatus(LinkStatus::ACTIVE)->withStatus(LinkStatus::PAUSED);
|
|
|
|
expect($link->canBeAccessed())->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('ownership', function () {
|
|
it('identifies owner correctly', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title,
|
|
userId: 'user123'
|
|
);
|
|
|
|
expect($link->isOwnedBy('user123'))->toBeTrue();
|
|
expect($link->isOwnedBy('user456'))->toBeFalse();
|
|
expect($link->isOwnedBy(null))->toBeFalse();
|
|
});
|
|
|
|
it('handles anonymous links', function () {
|
|
$link = SmartLink::create(
|
|
clock: $this->clock,
|
|
shortCode: $this->shortCode,
|
|
type: LinkType::RELEASE,
|
|
title: $this->title
|
|
);
|
|
|
|
expect($link->userId)->toBeNull();
|
|
expect($link->isOwnedBy('user123'))->toBeFalse();
|
|
});
|
|
});
|
|
});
|