Problem/Motivation

The rest_api_access_token module currently has zero test coverage (acknowledged as a TODO in README.md). This issue tracks adding comprehensive tests
across unit and kernel test layers. The module provides token-based authentication for REST APIs with login, logout, signature verification, response
caching, and token expiration via cron.

Proposed resolution

Approach

Tests should be split into two layers:

- Unit tests for classes with no Drupal dependencies (pure logic, value objects, generators)
- Kernel tests for classes that require the database, config system, entity storage, or the service container

Each test class maps to a single source class to keep coverage organized and discoverable.

Unit Tests

TokenTest (Model/Token.php)

- Verify constructor sets public, secret, and userId correctly
- Verify createdAt and refreshedAt default to current time when not provided
- Verify createdAt and refreshedAt use provided DateTime values when given
- Verify all getter methods return expected values

TokenGeneratorTest (Service/TokenGenerator.php)

- Verify execute() returns a Token with non-empty public and secret strings
- Verify generated public token is a valid SHA-256 hex string (64 characters)
- Verify generated secret is a valid SHA-256 hex string (64 characters)
- Verify execute() sets the correct userId on the returned Token
- Verify successive calls produce different tokens (statistical uniqueness)

DisallowXAuthTokenRequestsTest (PageCache/RequestPolicy/DisallowXAuthTokenRequests.php)

- Verify check() returns DENY when X-AUTH-TOKEN is present in request headers
- Verify check() returns DENY when X-AUTH-TOKEN is present as a query parameter
- Verify check() returns NULL when no X-AUTH-TOKEN is present

TokenResponseEventTest (Authentication/Event/TokenResponseEvent.php)

- Verify constructor stores Token and request content
- Verify hasAccess() returns TRUE by default
- Verify setHasAccess(FALSE) causes hasAccess() to return FALSE
- Verify setErrorMessage() and getErrorMessage() work correctly
- Verify getRequestContent() returns the original array

LogoutEventTest (Authentication/Event/LogoutEvent.php)

- Verify constructor stores Request and User
- Verify getRequest() and getUser() return expected objects

Kernel Tests

TokenRepositoryTest (Repository/TokenRepository.php)

- Verify install hook creates the rest_api_access_token table with correct schema
- Verify insert() persists a token and sets created_at/refreshed_at
- Verify getByPublicToken() returns the correct Token for a valid public token
- Verify getByPublicToken() throws TokenNotFoundException for a non-existent token
- Verify removeByPublicToken() deletes the correct token and returns 1
- Verify removeByPublicToken() returns 0 for a non-existent token
- Verify removeByUser() deletes all tokens for a given user
- Verify removeByUser() does not delete tokens belonging to other users
- Verify removeOtherUserTokens() removes all tokens for the user except the specified one
- Verify refresh() updates refreshed_at timestamp for the matching token
- Verify removeExpired() deletes tokens with refreshed_at older than the given DateTime
- Verify removeExpired() does not delete tokens refreshed after the given DateTime

LoginServiceTest (Service/LoginService.php)

- Verify login() with valid username and password returns a Token
- Verify login() with valid email and password returns a Token (when login_by_mail enabled)
- Verify login() throws AuthenticationException when login is empty
- Verify login() throws AuthenticationException when password is empty
- Verify login() throws AuthenticationException when user not found by name or email
- Verify login() throws AuthenticationException when password is incorrect
- Verify login() inserts the generated token into the repository
- Verify login() retries token generation when a collision occurs (up to 5 attempts)
- Verify logout() removes the token by public key and returns TRUE
- Verify logoutFromAllDevices() removes all tokens for the user

AccessTokenProviderTest (Authentication/Provider/AccessTokenProvider.php)

- Verify applies() returns TRUE when X-AUTH-TOKEN is in request headers
- Verify applies() returns TRUE when X-AUTH-TOKEN is in query parameters
- Verify applies() returns FALSE when no token is present
- Verify authenticate() returns the correct user for a valid token
- Verify authenticate() throws AccessDeniedException when token is empty
- Verify authenticate() throws AccessDeniedException when token not found in database
- Verify authenticate() throws AccessDeniedException when associated user is inactive
- Verify authenticate() refreshes token when refreshed_at is older than 60 seconds
- Verify authenticate() does not refresh token when refreshed_at is within 60 seconds
- Verify authenticate() throws InvalidRequestIdException when cache_endpoints is enabled but REQUEST-ID header is missing
- Verify authenticate() succeeds when signature_verification is disabled (no X-AUTH-SIGNATURE required)
- Verify authenticate() succeeds with a valid signature when signature_verification is enabled
- Verify authenticate() throws AccessDeniedException with an invalid signature when signature_verification is enabled
- Verify the signature is computed as sha256("token|requestId|path|base64(body)|secret")

AuthControllerTest (Controller/AuthController.php)

- Verify POST /api/v1/auth/token with valid credentials returns JSON with token, secret, and userId
- Verify POST /api/v1/auth/token with invalid credentials returns HTTP 400
- Verify POST /api/v1/auth/token dispatches TokenResponseEvent
- Verify POST /api/v1/auth/token returns HTTP 400 when event subscriber denies access
- Verify POST /api/v1/auth/logout removes the token used for authentication
- Verify POST /api/v1/auth/logout dispatches LogoutEvent
- Verify POST /api/v1/auth/logout-from-all-devices removes all user tokens
- Verify POST /api/v1/auth/logout-from-all-devices dispatches LogoutEvent with LOGOUT_FROM_ALL_DEVICES type

CacheEndpointSubscriberTest (EventSubscriber/CacheEndpointSubscriber.php)

- Verify onKernelRequest serves cached response when REQUEST-ID matches a cached entry
- Verify onKernelRequest does not serve cache when no REQUEST-ID is provided
- Verify onKernelRequest does not serve cache when cache_endpoints is disabled
- Verify onKernelResponse caches the response with the correct key when REQUEST-ID is present
- Verify cache lifetime of 0 results in permanent cache entries
- Verify cache lifetime of -1 disables caching entirely
- Verify cache lifetime > 0 sets correct expiration
- Verify getCacheKey() produces deterministic keys from requestId, token, and path

ConfigFormTest (Form/ConfigForm.php)

- Verify form renders all expected fields
- Verify submission saves all configuration values
- Verify validation rejects when neither login_by_name nor login_by_mail is selected
- Verify validation passes when at least one login method is selected

CronTest (rest_api_access_token.module cron hook)

- Verify cron removes tokens older than token_lifetime_hours
- Verify cron does not remove tokens when token_lifetime_hours is 0 (infinite)
- Verify cron does not remove tokens that have not yet expired

Out of scope (for now)

- Functional/browser tests for the admin config form (can be added later with DTT)
- Load testing for concurrent token generation
- Performance benchmarking of cache subscriber at high request volume

Acceptance criteria

- All listed test cases pass
- No new phpcs or phpstan violations in test files
- Tests can run via phpunit without requiring an external database seed or fixtures beyond what the kernel test base provides

Remaining tasks

Write tests and make an MR.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

benstallings created an issue. See original summary.

benstallings’s picture

Assigned: benstallings » Unassigned
Status: Active » Needs review