test(api): pin the merge-then-validate contract of the general settings endpoint - #3265
Open
marevol wants to merge 1 commit into
Open
test(api): pin the merge-then-validate contract of the general settings endpoint#3265marevol wants to merge 1 commit into
marevol wants to merge 1 commit into
Conversation
…gs endpoint PUT /api/admin/general is not a plain "store what was sent" update: it rebuilds the whole settings body from what is stored, overlays the request with copyBeanToBean(..., CopyOptions::excludeNull), validates the raw request against the bean constraints while validating the correlated SPNEGO rule against the merged result, and only then stores the merged body. That glue had no test anywhere. AdminGeneralActionTest covers the two shared halves (updateForm/updateConfig) and the integration suite's GeneralTests only reads, so nothing exercised the merge itself. Adds ApiAdminGeneralActionTest, which drives the real put$index execute method and pins: - an omitted spnego.preauth.password keeps the stored secret (mask rendered by updateForm, kept by excludeNull, skipped by updateConfig), while an explicitly empty one still clears it and a supplied one still replaces it - omitted plain fields keep their stored values, and a body carrying only the four @required fields is storable because the merge supplies the other three Integer fields updateConfig unboxes - the SPNEGO NTLM-prompt rule is evaluated against the merged result: it fires for a request that omits sso.type, spnego.allow.basic and spnego.prompt.ntlm when the stored combination is unsupported, and stands down both when the stored combination is fine and when the request supplies the missing half - the bean constraints are evaluated against the raw request, so a genuinely partial PUT is rejected and nothing is written; the correlated rule is not gated behind them, so both problems are reported in one response Each contract was checked against a deliberately broken copy of the action (excludeNull dropped, rule fed the raw body, raw body stored, merged body validated); every mutation is caught by the tests that own it. Test-only change; no production code is touched.
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.
Why
ApiAdminGeneralAction.put$index(PUT /api/admin/general) had no unit test, and it is not a plain "store what was sent" update:The two shared halves (
updateForm/updateConfig) are covered byAdminGeneralActionTest, and the integration suite'sGeneralTests.crudTest()only callstestRead()(createTestParam/getUpdateMaparefail(); // Unreachable). The merge-then-validate glue in between — which body each of the four steps sees — was covered nowhere.What this adds
src/test/java/org/codelibs/fess/app/web/api/admin/general/ApiAdminGeneralActionTest.java, 10 tests that drive the realput$indexexecute method (so the merge, the bean constraints and the correlated rule run in the action's own order):Masked secret, both directions
spnego_preauth_passwordkeeps the stored secret —updateFormrenders the**********mask,excludeNullkeeps it,updateConfigskips any all-mask valuespnego_preauth_passwordstill deletes the stored value (deliberate: the SPNEGO library only falls back to a keytab when both the pre-auth user name and password are empty), becauseexcludeNullexcludesnulland not""The merge fills in the request
@Requiredfields is storable at all, which it can only be because the merge supplied the other threeIntegerfieldsupdateConfigunboxesThe correlated rule reads the merged result
sso.type=spnego+spnego.allow.basic=false+spnego.prompt.ntlm=true, request omits all three → still rejected witherrors.spnego_prompt_ntlm_requires_basicspnegoAllowBasic=true) is accepted@Requiredis checked on the request@RequiredondayForCleanup,crawlingThreadCount,failureCountThreshold,csvFileEncoding, and nothing is writtenNote on one detail
While writing this I checked the assumption that a partial PUT is rejected "before the correlation check ever runs". That is not what happens:
ActionValidator.actuallyValidatecollects the Hibernate violations, then calls the extra-validation lambda into the same message set, and throws once at the end. So the correlated rule does run on a partial PUT and contributes its message alongside the@Requiredones. The last test pins that real behaviour (both messages in one response) rather than the assumed one.Scope covered / not covered
Covered end to end:
updateForm→excludeNullmerge →validateApi(bean constraints + correlated rule) →updateConfig, plus the stored state afterwards and theStatus.OKpayload.Not covered: LastaFlute's own translation of the thrown
ValidationErrorExceptioninto an HTTP 400 response body, which happens in the API failure hook after the execute method has returned; andisAccessAllowed(), whoseAccessTokenServicebinding is suppressed as in the sibling API action tests.Anti-vacuity check
Each contract was verified against a deliberately broken copy of the action in a scratch worktree; every mutation is caught, and the mutations are killed by the tests that own that contract:
copyBeanToBean(body, newBody)(noexcludeNull)isSpnegoNtlmPromptUnsupported(body)instead ofnewBodyupdateConfig(fessConfig, body)instead ofnewBodyvalidateApi(newBody, …)instead ofbody@RequiredtestsVerification
ApiAdminGeneralActionTestalone:Tests run: 10, Failures: 0, Errors: 0, Skipped: 0.mvn formatter:format && mvn license:formatproduced no changes.Test-only:
git diff master --statis the single new file.