Tests
The project uses PHPUnit. The suite runs by default on in-memory SQLite (see
phpunit.xml), so it does not need a MySQL database to run:
php artisan test
The suite covers bootstrap/installer/authentication, the local social domain
(posts, media, comments, reactions, follows, feed, notifications), identity and
social federation (tests/Feature/Federation,
tests/Unit/Infrastructure/Security), communities
(tests/Feature/Communities), and interoperability cases (Article/Video/
attachments, Pixelfed Atom fallback, Wafrn blog API, Lemmy Accept, World/
discover, profile photo roll). In particular:
- generation and verification of HTTP Signatures,
SsrfGuard(rejection of
private/loopback/reserved IPs, DNS that resolves to non-public addresses,
resolution failures), WebFinger, NodeInfo, content negotiation on
profile/post/comment (including visibility rules for anonymous requesters and
the Tombstone representation), outbox/followers/following collections, and
the full inbox lifecycle at the transport level (correctly signed activity,
missing signature, tampered body, signing Actor mismatch, unsupported
content-type, body too large, deduplication, shared inbox);
InboxActivityProcessor: every activity type (Followtoward open and
protected accounts, Accept/Reject of an outgoing follow, Undo,
Like/Announce and their Undo, Create of a relevant post or reply,
Delete, Update with a Note object and with a Person/Group object
including rejection of a document that declares an id different from the
signing Actor) and the case of an unknown signing Actor;
ActivityDeliveryandDeliverActivityJob: deduplication of shared inboxes,
exclusion of local/not-yet-accepted followers, delivery rules for direct
messages, correct HTTP signature of the outgoing request, permanent failure
without a private key, retry on a non-2xx response;
RemoteActorResolver::resolveByUri()/resolveByHandle(): fetch and cache
with TTL, rejection of a document that declares an id different from the one
requested, refusal to treat a local URI as remote, WebFinger resolution;
- the full "local action → activity delivered to a remote Actor" cycle
end-to-end for Follow/Unfollow, Like/Unlike, Announce/Unannounce,
publishing and deleting posts and comments (including the HTTP controllers);
- remote search (
/cerca) and the profile page of a cached remote Actor
(/attori/{id}, including the redirect to the canonical profile when the id
corresponds to a local Actor);
RemoteFollowCollectionsFetcher(RemoteFollowCollectionsFetcherTest):
visiting a remote profile queries the followers and following
collections (TTL OPENBOOK_COLLECTIONS_CACHE_TTL_HOURS, default 24h) for
totalItems counts and a first-page sample, without writing into the local
follows graph; join date comes from published on the Person document;
RemoteOutboxFetcher(RemoteOutboxFetcherTest): on the first load of a
remote Actor's profile page (or after cache expiry) the most recent public
posts from their real outbox are fetched and shown, excluding replies,
non-public posts, and any item that declares an author other than the outbox
owner; if the outbox is a stub (only totalItems, typical of Pixelfed) the
Atom feed is used; no new request before cache expiry; the attempt is still
recorded when the remote server does not respond, so later loads are not
slowed down; no mention notification is generated for content retrieved this
way (it is not a "just happened" event);
RemoteRepliesFetcher(RemoteRepliesFetcherTest,SignedFetchTest):
opening a remote post (e.g. from the feed of someone you follow) queries the
replies collection of the original Note (TTL
OPENBOOK_REPLIES_CACHE_TTL_HOURS), also following Mastodon's typical next
pagination (where the first page is often empty); the same Note GET updates
like/share card counts from likes/shares totalItems (at most one extra
GET per collection if the total is not inline); GETs are signed
(authorized fetch) with the visiting user's key or a fallback local Actor;
public/unlisted comments from third parties are cached without generating
notifications; replies to already-known comments under the same post are
nested correctly;
- follower/following lists (
FollowListTest): public visibility for a local
profile, exclusion of still-pending requests, correct follow/unfollow button
state per row, redirect of a remote Actor's list when it actually corresponds
to a local account, authentication required for a remote Actor's list.
- the Settings page (
SettingsTest): authentication required, update of
name/biography/links with synchronization of the name on the federated Actor,
upload and replacement of the avatar (with removal of the previous file),
rejection of a non-image file, change of interface language actually applied
by the middleware, propagation of default visibility to the composer,
synchronization of "protected account" between user_settings and the Actor,
exclusion from suggestions when the account is no longer "discoverable",
sending of a federated Update to remote followers when the public profile,
the "Protected account" option, or the discoverable/indexable flags
change (and its absence when only purely local preferences change); the
profile image upload service (ProfileImageUploaderTest): separate paths for
avatar/cover, removal of the previous file, type and size validation,
resizing of oversized images, permissions of the directory created on first
upload correct even with a restrictive PHP-process umask (also verified for
post attachments in MediaUploaderTest); construction of the avatar/cover
URL (Tests\Unit\Domain\Profiles\ProfileTest), to avoid regressions on the
choice of the "public" disk instead of the asset() helper.
- the "World" section (
WorldTest): the timeline shows only public remote
posts already in cache and excludes both local posts and non-public remote
ones; authentication required; ranking of accounts to discover (priority to
accepted local followers, then to the most recent activity), exclusion of
anyone with neither a local follower nor a post in cache, exclusion of anyone
already followed by the visitor; /mondo/scopri page with the full list and
infinite scroll.
- share visibility (
AnnounceVisibilityTest): a shared post (local, from
another local Actor, or remote) appears on the profile and in the personal
feed of the person who shared it with the "shared this post" label; ordering
by share time even when the original post is much older; no redundant label
when sharing your own post; disappearance from the profile after undoing the
share; no label on the original author's profile.
A small subset of tests (Tests\Feature\Installer\InstallerMysqlFlowTest)
specifically checks installer step 2 (connection and migration) against a
real MySQL/MariaDB server, because this behavior cannot be exercised
reliably with SQLite. These tests skip themselves (markTestSkipped) if they
do not find a reachable server with the credentials given by the environment
variables OPENBOOK_TEST_MYSQL_HOST, OPENBOOK_TEST_MYSQL_PORT,
OPENBOOK_TEST_MYSQL_DATABASE, OPENBOOK_TEST_MYSQL_USERNAME,
OPENBOOK_TEST_MYSQL_PASSWORD (sample values already present in
phpunit.xml). To actually run them, start a throwaway MySQL/MariaDB instance
with those credentials before launching the suite.