Skip to content

Bug 2020752 - [PHABBUGZ] Improve testing of phabricator and bmo integration - #2790

Merged
dklawren merged 3 commits into
mozilla:masterfrom
dklawren:2020752
Oct 20, 2026
Merged

dklawren merged 3 commits into
mozilla:masterfrom
dklawren:2020752

Conversation

@dklawren

@dklawren dklawren commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Add a mocked PhabBugz test suite and seed approval-mozilla-* flag types

Summary

Adds a test suite for the Bugzilla/Phabricator integration that runs without
a Phabricator server. Every Conduit call goes to an in-process fake that
answers with the response shapes from the Conduit API docs. The tests live in
extensions/PhabBugz/t/bmo/, so CI's existing test_bmo job picks them up
and runs them against the real MySQL database. No CI changes are needed.

Also seeds the approval-mozilla-* flag types in generate_bmo_data.pl, so
set_attachment_approval_flags can be tested. This includes the common
production failure where the flag is not enabled for the bug's
product/component.

No production code is changed.

Changes

Test helpers (extensions/PhabBugz/t/bmo/lib/PhabTest/)

  • Conduit.pm: a fake Conduit server. It overrides Mojo::UserAgent::post
    and records every call. It answers from an in-memory directory of users,
    projects, revisions, repositories, policies, feed stories, transactions and
    edges, and applies *.edit calls to that directory. Tests can queue
    failures: Conduit errors, HTTP errors, non-JSON bodies, network errors and
    an invalid feed cursor. A call to a method the fake doesn't handle fails the
    test.
  • Fixtures.pm: builders for complete Conduit objects.
  • Env.pm: per-file setup and teardown for the shared database. It creates
    fixtures with unique names, restores the phabbugz cursors, deactivates
    test groups, and only ever sets PhabBugz params in memory.

Tests (13 files)

  • conduit-request.t: how request() encodes calls, and how it handles
    missing config, HTTP 4xx/5xx, bad or truncated JSON, Conduit errors (with
    and without no_die), network failures and a missing result.
  • model-{user,project,policy,revision,repository-comment}.t: lookups,
    paging, lazy builders, the exact transactions each mutator sends, privacy
    helpers, and errors from Phabricator. model-user.t also covers syncing a
    disabled BMO account to Phabricator.
  • util.t: bug role PHIDs, revision attachments, auto-assignment, reviewer
    rotation and intermittent reviewers.
  • webservice.t: bug_revisions, user and the phab-bot permission checks.
  • approval-flags.t:
    • ? on new revisions, + only from release managers, X handling
    • legacy repo name mapping, disabled flags, unknown repos
    • the flag not enabled for the bug's product or component, both called
      directly and through the feed
  • feed-stories.t, feed-users.t, feed-groups.t: the daemon's periodic
    loops, covering:
    • cursor handling and paging
    • skipped stories
    • public, private and invalid bugs
    • uplift forms
    • new-user subscriptions and username-squatter alerts
    • group project sync
    • rollback and failure handling, including invalid-cursor retries
  • feed-run-query.t: the daemon wrapper logs errors, clears the watchdog
    alarm and keeps running.

Seed data (scripts/generate_bmo_data.pl)

  • approval-mozilla-{beta,release,esr140}: enabled only for
    Firefox :: General, so bugzilla.mozilla.org and Firefox :: Installer
    reproduce the "flag not enabled" case.
  • approval-mozilla-esr115: seeded but disabled (is_active = 0).
  • The insert loop takes an optional is_active, defaulting to 1. The existing
    flag types are unchanged.

Issues found (left as TODO tests, not fixed here)

  1. One failing story stalls the feed for good. feed_query has no
    per-story error handling, so the cursor never moves past the failing story.
    The most likely trigger is an approval flag that isn't enabled for the
    bug's product/component: set_flags throws invalid_flag_association and
    the whole story is rolled back.
  2. Project::update never sends the join policy.
  3. Group sync only re-applies project policies when membership changes.
  4. revision/comments.html.tmpl doesn't load terms. The
    invalid-bug comment posted to Phabricator reads "invalid ID", with the
    word "bug" missing.

Also noted, but tested as current behavior rather than as TODOs:

  • Policy->create([]) sends a fallback rule without a rule key.
  • A new revision in needs-revision creates an approval - flag without a
    release manager check.

Testing

docker compose -f docker-compose.test.yml down
docker compose -f docker-compose.test.yml run -e CI=1 bmo.test test_bmo t/bmo/*.t extensions/*/t/bmo/*.t
  • test_bmo: 40 files, 1,786 tests pass. The PhabBugz suite was also run
    twice in one database session to check that it cleans up after itself.
  • test_webservices: 45 files, 3,907 tests pass with the new seed data.
  • test_sanity: the critic, goodperl and whitespace checks pass.
  • Selenium tests: not run.

🤖 Generated with Claude Code

@dklawren
dklawren requested review from Xzzz and cgsheeh and a balanced review from Copilot October 18, 2026 18:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Fixture ID collisions and unrealistic attachment responses undermine test isolation and integration coverage.

2 open issues
What changed in this PR

Expands PhabBugz integration testing against BMO using an in-process fake Phabricator Conduit service.

Changes:

  • Adds reusable fixtures, request simulation and test-environment helpers.
  • Covers models, synchronization, permissions, approval flags and failure handling.
  • Seeds product-scoped and disabled uplift approval flags.
File Description
scripts/​generate_bmo_data.pl Seeds uplift approval flag types.
extensions/​PhabBugz/​t/​bmo/​webservice.t Tests endpoints and permissions.
extensions/​PhabBugz/​t/​bmo/​util.t Tests attachments and reviewer helpers.
extensions/​PhabBugz/​t/​bmo/​model-user.t Tests account lookup and synchronization.
extensions/​PhabBugz/​t/​bmo/​model-revision.t Tests revision models and privacy helpers.
extensions/​PhabBugz/​t/​bmo/​model-repository-comment.t Tests repository and comment models.
extensions/​PhabBugz/​t/​bmo/​model-project.t Tests project lookup and updates.
extensions/​PhabBugz/​t/​bmo/​model-policy.t Tests custom policy handling.
extensions/​PhabBugz/​t/​bmo/​lib/​PhabTest/​Fixtures.pm Builds Conduit fixtures.
extensions/​PhabBugz/​t/​bmo/​lib/​PhabTest/​Env.pm Provides setup and teardown helpers.
extensions/​PhabBugz/​t/​bmo/​lib/​PhabTest/​Conduit.pm Simulates and records Conduit requests.
extensions/​PhabBugz/​t/​bmo/​feed-users.t Tests new-user synchronization.
extensions/​PhabBugz/​t/​bmo/​feed-stories.t Tests revision synchronization and failures.
extensions/​PhabBugz/​t/​bmo/​feed-run-query.t Tests daemon error handling and cleanup.
extensions/​PhabBugz/​t/​bmo/​feed-groups.t Tests group membership synchronization.
extensions/​PhabBugz/​t/​bmo/​conduit-request.t Tests transport encoding and errors.
extensions/​PhabBugz/​t/​bmo/​approval-flags.t Tests uplift approval flag behavior.

Review effort: Balanced

Comment thread extensions/PhabBugz/t/bmo/lib/PhabTest/Conduit.pm Outdated
Comment thread extensions/PhabBugz/t/bmo/lib/PhabTest/Fixtures.pm Outdated

@cgsheeh cgsheeh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments. As this PR is just adding new tests, I'm fine with landing as-is and we can iterate. :)


sub reply_feed_query_id {
my ($self, $params) = @_;
my $after = $params->{after} // 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real feed.query_id errors when after isn't a valid story id (that's why new_stories has the retry loop), but this fake returns everything above after. Does that let tests like the after => 1 and 7_000_000 cursor cases pass where production would retry or throw? Should the fake return cursor_invalid when after doesn't match a story (other than the initial cursor)?

$conduit->queue('feed.query_id' => cursor_invalid($cursor));
ok(!run_feed(), 'feed_query recovers from an invalid cursor');
is_deeply(
[map { $_->{after} } $conduit->calls('feed.query_id')],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With gap => 2 there's no story at $cursor + 1, so by real Conduit's behaviour wouldn't this need a third retry? Is this assertion only passing because the fake ignores unknown after ids?


sub reply_feed_query_id {
my ($self, $params) = @_;
my $after = $params->{after} // 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores page_size, unlike _page and user.search. Should it honour the limit so we can test the "one page per run, rest on the next run" behaviour for stories like feed-users.t does for users?

view => $args{view_policy} // 'public',
edit => $args{edit_policy} // 'users',
},
'bugzilla.bug-id' => $args{bug_id} // '',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Conduit returns null for an unset bugzilla.bug-id custom field. Should this default to undef so the "missing bug id becomes an empty string" test in model-revision.t actually exercises the null → '' conversion in Revision::new_from_query?

Comment thread extensions/PhabBugz/t/bmo/util.t Outdated
# Only NEW and UNCONFIRMED bugs move to ASSIGNED; BMO files bugs as
# CONFIRMED, which is left alone.
is(
$bug->bug_status,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since test bugs are filed as CONFIRMED, this only ever checks "status unchanged". Should we also file a NEW/UNCONFIRMED bug so the ASSIGNED transition in set_bug_assignee gets covered?

ok(!run_feed(), 'story after a group was added to the bug');
my ($recreate) = $conduit->calls('policy.create');
is_deeply(
[sort @{$recreate->{policy}[-1]{value}}],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If policy.create stops being called, $recreate is undef and this dereference kills the whole file. Should we assert ok($recreate, ...) first?

'form not copied again'
);
is(scalar(phab_attachments($bug->id)), 1, 'normal sync ran');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cleanup only runs on the success path. Should it go through $env->on_teardown like the other cleanups?

use Digest::SHA qw(sha1_hex);
use Storable qw(dclone);
use Test::More;
use Try::Tiny;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Try::Tiny used in this file?

use Bugzilla;
BEGIN { Bugzilla->extensions }

use Storable qw(dclone);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is dclone used here?

grant_group => '',
target_type => 'a',
cc_list => '',
inclusions => ['Firefox:General'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These flags will now show up on attachments for every Firefox :: General bug in dev/test DBs. Given Selenium wasn't run, is it worth running it to make sure nothing there depends on the attachment flag list?

@Xzzz Xzzz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Nothing to add to Connor's points. lgtm once CI runs successfully

@dklawren
dklawren merged commit a45200d into mozilla:master Oct 20, 2026
8 checks passed
@dklawren
dklawren deleted the 2020752 branch October 20, 2026 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants