• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

akvo / akvo-mis / #781
89%
main: 89%

Build:
Build:
LAST BUILD BRANCH: feature/274-admin-bulk-upload-hardening
DEFAULT BRANCH: main
Ran 01 Aug 2026 09:56AM UTC
Jobs 1
Files 113
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

01 Aug 2026 09:35AM UTC coverage: 88.826% (+0.04%) from 88.786%
#781

Pull #284

coveralls-python

zuhdil
[#272] Answer a lost level-add race with 400, not 500

Copilot's review of the levels screen raised four points; three of them
were real and are addressed here.

`perform_create` reads the tenant's deepest level and writes max + 1
with nothing holding the gap, so two adds in flight together both pick
the same depth and the loser trips `unique_level_per_tenant`. That
bubbled out of DRF as a 500. It is now caught and answered as a 400
telling the caller to reload.

The savepoint around the create is not decoration: an `IntegrityError`
caught without one leaves the connection unusable for the remainder of
the transaction, so the rejection could not be built at all if a
transaction were ever open around the request. Locking the levels table
instead would have been the wrong trade for a screen a tenant uses once,
during onboarding.

Reaching that race from the UI took no contrivance. Both Save buttons
carry `loading={saving}`, which antd uses to swallow clicks while a
request is in flight, but the inputs' `onPressEnter` had no such
protection — two quick presses of Enter were a live double-submit. The
handlers now refuse to fire while a save is outstanding.

The same handlers trim the name and refuse a blank result. This is not
the validation; DRF's `CharField` already trims and rejects, which is
why a name of spaces could never have been stored. It is about what the
person sees: previously "   " passed the `!newName` guard, cost a round
trip, and came back as the generic error toast rather than as a button
that had stayed disabled.

Copilot's fourth point — that anchoring `/levels` also stopped it
matching `/levels/` — describes a real change in behaviour, but nothing
calls the endpoint with a trailing slash. The web client uses
`api.get("levels")` and the mobile app never touches it. Precision was
the reason for the anchor, so it stands.

Both new tests were checked against the unfixed code first: without the
savepoint the race test errors out, and witho... (continued)
Pull Request #284: [#272] Let a tenant build its hierarchy's depth

5751 of 6647 branches covered (86.52%)

Branch coverage included in aggregate %.

10919 of 12120 relevant lines covered (90.09%)

0.9 hits per line

Coverage Regressions

Lines Coverage ∆ File
16
92.33
1.39% api/v1/v1_profile/views.py
15
93.12
0.15% api/v1/v1_profile/serializers.py
Jobs
ID Job ID Ran Files Coverage
1 #781.1 01 Aug 2026 09:56AM UTC 113
88.83
Source Files on build #781
  • Tree
  • List 113
  • Changed 3
  • Source Changed 0
  • Coverage Changed 3
Coverage ∆ File Lines Relevant Covered Missed Hits/Line Branch Hits Branch Misses
  • Back to Repo
  • Pull Request #284
  • PR Base - epic/multi-tenant-saas (#)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc