Test the farmer and plot endpoints, tenant isolation included - #27
Open
padovan wants to merge 1 commit into
Open
Test the farmer and plot endpoints, tenant isolation included#27padovan wants to merge 1 commit into
padovan wants to merge 1 commit into
Conversation
A farmer record is the most sensitive thing INATrace stores: a named smallholder, their phone number, their bank account and the coordinates of the land they work. Every farmer and plot endpoint on CompanyController takes an id straight from the URL or the request body, so the only thing between one cooperative and another's farmer register is the enrolment check in CompanyService. Twenty tests hold that line from both sides. Each endpoint is asked once by someone entitled to the data and once by a stranger, and the stranger's response is checked not only for a 403 but for the absence of the data itself -- the seeded bank account, GeoID and coordinates are distinctive strings, so an endpoint that refused in the header while writing rows into the body, or streamed an export before the check ran, would still fail. Covered: the farmer list and its scoping, a farmer by id, the company plots list, plot creation, the GeoID refresh, geo-data export and upload, the farmer data zip, and the add, edit and delete writes. Two cases go past enrolment: a plot id is asserted to resolve against the farmer in the path rather than globally, and every endpoint is asked anonymously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016mn32g3xvTVhT79ZVEbfUe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A farmer record is the most sensitive thing INATrace stores: a named
smallholder, their phone number, their bank account, and the satellite
coordinates of the land they work. Every farmer and plot endpoint on
CompanyControllertakes an id straight from the URL or the request body, sothe only thing standing between one cooperative and another's farmer register
is the enrolment check in
CompanyService.This adds 20 tests that hold that line from both sides. Each endpoint is asked
once by someone entitled to the data and once by a stranger.
The refusals are checked for more than a status code
Asserting 403 alone would not catch an endpoint that refused in the header and
still wrote the rows into the body, or an export that streamed its file before
the check ran. So the fixture seeds distinctive markers — a bank account
number, a GeoID, a pair of coordinates — and every refused response is searched
for them.
Covered
GET /userCustomers/{companyId}/{type}GET /userCustomers/{id}GET /userCustomers/{companyId}/plotsGET /userCustomers/{companyId}/exportFarmerDataGET /userCustomers/{id}/exportGeoDataPOST /userCustomers/{id}/uploadGeoDataPOST /userCustomers/add/{companyId}PUT /userCustomers/editDELETE /userCustomers/{id}POST /userCustomers/{id}/plots/addPOST /userCustomers/{id}/plots/{plotId}/updateGeoIDEach refused write is also checked to have written nothing: the farmer's
surname is unchanged after a rejected edit, the record survives a rejected
delete, and the plot count is unchanged after a rejected plot or GeoJSON upload.
Two cases go past enrolment. A listing is asserted to be scoped rather than
merely gated — the other tenant's farmer must not appear in a response the
caller is entitled to. And a plot id is asserted to resolve against the farmer
named in the path rather than globally: the caller owns both farmers, so a
mismatched farmer/plot pair isolates the lookup rather than the permission, and
must answer 400 rather than updating someone else's plot.
Finally, every endpoint is asked with no session at all and must answer 401.
Notes
src/mainis touched.MultiTenantIsolationTest:@SpringBootTestwith MockMvcover the real filter chain, a real JWT from
TokenServicein the accesscookie, and a MySQL container via
@ServiceConnection.@Transactional, and the seed ends with a flush and clear so everyrequest loads its entities from the database as production does. That matters
here:
UserCustomer.getAssociations()and its neighbours, unlikegetPlots(), do not initialise themselves when the field is null, so anobject left in the session from the seed would fail in the mapper for a
reason no real request ever meets.
🤖 Generated with Claude Code
https://claude.ai/code/session_016mn32g3xvTVhT79ZVEbfUe