Skip to content

chore: add test:unit:filter and test:integration:filter scripts - #5181

Merged
imorland merged 1 commit into
2.xfrom
im/test-filter-scripts
Oct 12, 2026
Merged

imorland merged 1 commit into
2.xfrom
im/test-filter-scripts

Conversation

@imorland

@imorland imorland commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Fixes: no issue.

Changes proposed in this pull request:
Running one test class meant either the whole suite (core's integration suite is over 1,000 tests and takes several minutes) or calling PHPUnit by hand with --filter. This adds test:unit:filter and test:integration:filter to core and to every bundled extension with tests, used as composer test:integration:filter -- ClassName (or a method name, or 'ClassA|ClassB'). messages, mentions and suspend already had the integration one; it is moved next to test:integration, so every package lists the scripts in the same order.

New extensions get the same scripts from the CLI in flarum/cli#63, and the testing guide documents them in flarum/docs#664.

Reviewers should focus on:

  • Scripts only: 19 composer.json files, nothing else changed in them.

Screenshot
N/A

Necessity

  • Has the problem that is being solved here been clearly explained?
  • If applicable, have various options for solving this problem been considered? (N/A)
  • For core PRs, does this need to be in core, or could it be in an extension? (N/A, dev scripts.)
  • Are we willing to maintain this for years / potentially forever?

Confirmed

  • Frontend changes: tested on a local Flarum installation. (No frontend changes.)
  • Frontend changes: tests are green (run yarn test in js/). (No frontend changes.)
  • Frontend changes: tests have been added, or are not appropriate here. (No frontend changes.)
  • Backend changes: tests are green (run composer test). (No code changes; ran test:unit:filter in core and test:integration:filter in sticky.)
  • Backend changes: tests have been added, or are not appropriate here. (No code changes.)
  • Where applicable, changes are suitable for all supported database drivers (MySQL, MariaDB, PostgreSQL, SQLite). (N/A)
  • Core developer confirmed locally this works as intended.
  • The description above is written by me and describes what this pull request actually does.

Every package with tests can now run one class or method through its
composer scripts: composer test:integration:filter -- ClassName. The
integration filter messages, mentions and suspend already had is moved
next to test:integration, so all packages list the scripts in one order.
@imorland
imorland requested a review from a team as a code owner October 12, 2026 10:49
@imorland imorland added this to the 2.0.1 milestone Oct 12, 2026
@imorland
imorland merged commit f59d33b into 2.x Oct 12, 2026
30 checks passed
@imorland
imorland deleted the im/test-filter-scripts branch October 12, 2026 11:07
imorland added a commit that referenced this pull request Oct 31, 2026
Brings the new extension in line with the other bundled packages on 2.x:
the 2.1 constraints from #5255 and the test:*:filter scripts from #5181.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
imorland added a commit that referenced this pull request Oct 31, 2026
Brings the new extension in line with the other bundled packages on 2.x:
the 2.1 constraints from #5255 and the test:*:filter scripts from #5181.
imorland added a commit that referenced this pull request Oct 31, 2026
…an extender for features (#5111)

* feat(ai): start the flarum/ai extension

* chore(ai): require flarum/core ^2.1 and add the test filter scripts

Brings the new extension in line with the other bundled packages on 2.x:
the 2.1 constraints from #5255 and the test:*:filter scripts from #5181.

* feat(ai): set up connections on the admin page, with write-only API keys

The extension now has an admin page listing its connections: each one a
provider (Anthropic, OpenAI, Gemini, Mistral, Groq, DeepSeek, xAI,
OpenRouter, Ollama or an OpenAI-compatible server), an optional API URL and
an API key, plus the default connection. Connections set in config.php are
listed too, read-only.

API keys are write-only. The admin payload carries every setting, so a
Deserializing listener swaps the stored connections for a copy with
`hasKey` in place of each key. A connection saved without a key keeps the
stored one, as long as its provider hasn't changed; an empty key removes it.
Connections are validated on Saving, before any setting is written, so a
rejected save leaves every setting as it was.

* feat(ai): usage ledger, token limits, and an extender for AI features

Every laravel/ai call, from any extension or none, now goes through two
listeners: one stops it before it's made if a limit is used up, the other
records it in a new `ai_usage` table once it's done. A row holds what the
call was for, the member it was made for, the connection, model and token
counts; never the prompt or the reply. Rows are pruned after 90 days by a
daily `ai:prune-usage`.

Admins can limit tokens per connection each month, per feature each day,
and per member each day (admins have no member limit). A limit stops the
next call once it's used up, so it can be overshot by one call. The
connection modal gains a default model and a monthly limit; the page gains
the member limit, and this month's usage by connection, feature and member,
with the last 30 days.

Extensions register what they use AI for with `Extend\Ai::feature()` and
make calls through `Ai::feature($key)->as($actor)->prompt($agent, $text)`.
On the admin page each feature is off until turned on, and can have its own
connection, model and daily limit; members need its permission, which is
listed with the others. Calls are attributed to the feature and member, and
a disabled feature or one with no connection throws before any call.

* refactor(ai): lay the admin page out in native tabs and sections

Adopts the settings tabs and sections from #5346. The page now has
Connections, Features and Usage tabs, then core's Permissions tab, which
lists each feature's permission. Each tab is a sectioned form with the
sticky save bar; one save still covers every tab, and a tab with unsaved
changes shows the dot core's tabs use.

Usage loads when its tab is first opened, and a failed load is shown on the
page only, without the request error modal on top of it.

* chore(ai): add the built admin bundle and typings

The backend tests render /admin, which needs js/dist/admin.js, and backend CI
doesn't build JS. Like flarum/deck's first PR, the extension ships its first
bundle; the bundled-output workflow keeps it up to date from here.

* refactor(ai): address review: key storage, limits, attribution and the feature API

Keys
- API keys move out of settings into an ai_connection_keys table, so they
  never reach the admin payload, whether or not the extension is enabled. A
  laravel/ai manager subclass reads a connection's key only when its provider
  is first built. Key changes from a save are written on Saved, after every
  setting in it, so a save another extension rejects leaves them as they were.
- A saved key is kept only while the connection's provider and API URL are
  unchanged; a new address needs the key typed again. Removing a connection
  removes its key.
- Connection names must start with a letter, so none is decoded from JSON as
  a number. URLs of config.php connections are shown without credentials.

Limits and usage
- Every kind of laravel/ai call is limited and recorded: streams
  (StreamingAgent), embeddings, images, audio, transcription, reranking and
  classification. Text is recorded per completed step, so failed calls,
  failovers and abandoned streams count what they spent.
- Usage records who a call was for (member, guest or the forum itself), and
  guests share a daily limit. LimitReachedException says which limit and when
  it resets, also in the 429's meta, and lets laravel/ai fail over.
- Agents given to a feature call are remembered, so streams read later and
  queued calls are counted for their feature and member.

Feature API
- Calls must say who they're for: as($actor), or asSystem() for the forum's
  own work. FeatureCall gains stream() and queue().
- Exceptions live in Flarum\Ai\Exception and share UnavailableException:
  FeatureDisabledException (403, ai_feature_disabled), NotConfiguredException
  (503, ai_not_configured) and LimitReachedException (429, ai_limit_reached).
- Feature keys are checked when registered, including the length the
  permission column allows; registering one twice names both extensions.
- The forum payload lists the features the viewer can use (aiFeatures).
- InteractsWithAi helps extensions' tests set up a connection and features.
- Internal classes are marked @internal.

Admin page
- The usage totals load once instead of on every redraw, with a retry after
  a failed load; unsaved tabs go through core's unsavedTabs().
- Labelled fields, focus on the field a check failed on, strict number
  parsing, a text summary of the chart, permissions labelled "Use AI: …",
  the feature's extension named, settings in the admin search, and setting
  defaults so a fresh install doesn't start with unsaved changes.
- JS tests for the settings helpers, the page and the connection form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant