package jnpf.elnController; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.base.ActionResult; import jnpf.elnEntity.template.ElnTemplateForms; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnEntity.formula.ElnFormulaForms; import jnpf.elnEntity.formula.ElnFormulaVos; import jnpf.elnService.formula.ElnTemplateFormulaBindingService; import jnpf.elnService.template.ElnTemplateBindingService; import jnpf.elnService.template.ElnTemplateOperationException; import jnpf.elnService.template.TemplateAnnotationAuthorization; import jnpf.exception.DataException; import jnpf.model.tenant.TenantVO; import jnpf.util.NoDataSourceBind; import jnpf.util.TenantHolder; import jnpf.util.context.SpringContext; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.http.ResponseEntity; import org.springframework.context.support.StaticApplicationContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ElnTemplateAnnotationControllerTest { @BeforeClass public static void setUpSpringContext() { SpringContext.setApplicationContext(new StaticApplicationContext()); } @After public void clearTenant() { TenantHolder.clearLocalTenantCache(); } @Test public void exposesOnlyTicketRoutesAndNoDataSourceBinding() throws Exception { assertEquals("/onlyoffice/templateAnnotation", ElnTemplateAnnotationController.class.getAnnotation(RequestMapping.class).value()[0]); assertTrue(ElnTemplateAnnotationController.class.isAnnotationPresent(NoDataSourceBind.class)); assertGet("context", "/context", String.class); assertPost("suggestCode", "/codeSuggestions", String.class, ElnTemplateForms.CodeSuggestion.class); assertGet("crossTemplateSourceTemplates", "/crossTemplateSourceTemplates", String.class); assertGet("crossTemplateScalarCandidates", "/crossTemplateTextCandidates", String.class, String.class); assertPost("reserveCrossTemplateScalarBinding", "/crossTemplateTextBindings/reserve", String.class, ElnTemplateForms.ReserveCrossTemplateScalarBinding.class); assertPost("reservePrivateField", "/privateFields/reserve", String.class, ElnTemplateForms.PrivateField.class); assertPost("reserveBinding", "/bindings/reserve", String.class, ElnTemplateForms.ReserveBinding.class); assertPost("reserveReference", "/bindings/{id}/references/reserve", String.class, String.class, ElnTemplateForms.Operation.class); assertPost("changeCheckboxGroup", "/bindings/{id}/checkboxGroup", String.class, String.class, ElnTemplateForms.ChangeCheckboxGroup.class); assertPost("changeCheckboxGroupRule", "/checkboxGroups/{groupCode}", String.class, String.class, ElnTemplateForms.ChangeCheckboxGroupRule.class); assertPost("activate", "/bindings/{id}/activate", String.class, String.class, ElnTemplateForms.Operation.class); assertPost("cancel", "/bindings/{id}/cancel", String.class, String.class, ElnTemplateForms.Cancel.class); assertGet("repairContext", "/bindings/{id}/repairContext", String.class, String.class); assertPost("confirmRepair", "/bindings/{id}/repairConfirm", String.class, String.class, ElnTemplateForms.RepairConfirm.class); assertPost("checkUpgrade", "/bindings/{id}/versionUpgradeCheck", String.class, String.class, ElnTemplateForms.UpgradeCheck.class); assertPost("confirmUpgrade", "/bindings/{id}/versionUpgradeConfirm", String.class, String.class, ElnTemplateForms.UpgradeConfirm.class); assertPost("checkRemove", "/bindings/{id}/removeCheck", String.class, String.class); assertPost("confirmRemove", "/bindings/{id}/removeConfirm", String.class, String.class, ElnTemplateForms.RemoveConfirm.class); assertPost("checkPromote", "/bindings/{id}/promoteCheck", String.class, String.class); assertPost("confirmPromote", "/bindings/{id}/promoteConfirm", String.class, String.class, ElnTemplateForms.PromoteConfirm.class); assertPost("confirmVersionCopy", "/versionCopy/confirm", String.class, ElnTemplateForms.VersionCopyConfirm.class); assertGet("formulas", "/formulas", String.class); assertGet("formulaContext", "/formulaContext", String.class); assertPost("reserveFormulaBinding", "/formulaBindings/reserve", String.class, ElnFormulaForms.TemplateBinding.class); assertPost("activateFormulaBinding", "/formulaBindings/{id}/activate", String.class, String.class, ElnFormulaForms.ActivateFormulaBinding.class); assertPost("cancelFormulaBinding", "/formulaBindings/{id}/cancel", String.class, String.class, ElnFormulaForms.CancelFormulaBinding.class); assertPost("checkFormulaUpgrade", "/formulaBindings/{id}/upgradeCheck", String.class, String.class, ElnFormulaForms.TemplateBinding.class); assertPost("confirmFormulaUpgrade", "/formulaBindings/{id}/upgradeConfirm", String.class, String.class, ElnFormulaForms.UpgradeMappingConfirm.class); assertPost("checkFormulaRemove", "/formulaBindings/{id}/removeCheck", String.class, String.class); assertPost("confirmFormulaRemove", "/formulaBindings/{id}/removeConfirm", String.class, String.class, ElnFormulaForms.RemoveMappingConfirm.class); assertPost("confirmFormulaVersionCopy", "/formulaBindings/versionCopy/confirm", String.class, ElnFormulaForms.FormulaVersionCopyConfirm.class); assertOptionalBody("reservePrivateField", String.class, ElnTemplateForms.PrivateField.class); assertOptionalBody("suggestCode", String.class, ElnTemplateForms.CodeSuggestion.class); assertOptionalBody("reserveCrossTemplateScalarBinding", String.class, ElnTemplateForms.ReserveCrossTemplateScalarBinding.class); assertOptionalBody("reserveBinding", String.class, ElnTemplateForms.ReserveBinding.class); assertOptionalBody("reserveReference", String.class, String.class, ElnTemplateForms.Operation.class); assertOptionalBody("changeCheckboxGroup", String.class, String.class, ElnTemplateForms.ChangeCheckboxGroup.class); assertOptionalBody("changeCheckboxGroupRule", String.class, String.class, ElnTemplateForms.ChangeCheckboxGroupRule.class); assertOptionalBody("activate", String.class, String.class, ElnTemplateForms.Operation.class); assertOptionalBody("cancel", String.class, String.class, ElnTemplateForms.Cancel.class); assertOptionalBody("confirmRepair", String.class, String.class, ElnTemplateForms.RepairConfirm.class); assertOptionalBody("checkUpgrade", String.class, String.class, ElnTemplateForms.UpgradeCheck.class); assertOptionalBody("confirmUpgrade", String.class, String.class, ElnTemplateForms.UpgradeConfirm.class); assertOptionalBody("confirmRemove", String.class, String.class, ElnTemplateForms.RemoveConfirm.class); assertOptionalBody("confirmPromote", String.class, String.class, ElnTemplateForms.PromoteConfirm.class); assertOptionalBody("confirmVersionCopy", String.class, ElnTemplateForms.VersionCopyConfirm.class); assertOptionalBody("reserveFormulaBinding", String.class, ElnFormulaForms.TemplateBinding.class); assertOptionalBody("activateFormulaBinding", String.class, String.class, ElnFormulaForms.ActivateFormulaBinding.class); assertOptionalBody("cancelFormulaBinding", String.class, String.class, ElnFormulaForms.CancelFormulaBinding.class); assertOptionalBody("checkFormulaUpgrade", String.class, String.class, ElnFormulaForms.TemplateBinding.class); assertOptionalBody("confirmFormulaUpgrade", String.class, String.class, ElnFormulaForms.UpgradeMappingConfirm.class); assertOptionalBody("confirmFormulaRemove", String.class, String.class, ElnFormulaForms.RemoveMappingConfirm.class); assertOptionalBody("confirmFormulaVersionCopy", String.class, ElnFormulaForms.FormulaVersionCopyConfirm.class); } @Test public void formulaControlSnapshotJsonBindsControlRole() throws Exception { ElnFormulaForms.ActivateFormulaBinding form = new ObjectMapper().readValue( "{\"controls\":[{\"bindingId\":\"binding-1\"," + "\"controlTag\":\"eln.field.binding-1\"," + "\"controlRole\":\"REFERENCE\"," + "\"lock\":\"sdtContentLocked\"}]}", ElnFormulaForms.ActivateFormulaBinding.class); assertEquals("REFERENCE", form.getControls().get(0).getControlRole()); } @Test public void formulaReserveJsonBindsOriginalResultLockSnapshotOnlyOnReserveForm() throws Exception { ElnFormulaForms.TemplateBinding form = new ObjectMapper().readValue( "{\"formulaVersionId\":\"formula-version\",\"resultBindingId\":\"result-binding\"," + "\"operationCode\":\"reserve-op\",\"jieguoSuoKuaizhao\":[{" + "\"bindingId\":\"result-binding\",\"controlTag\":\"eln.field.result\"," + "\"controlRole\":\"PRIMARY\",\"lock\":\"unlocked\"}]}", ElnFormulaForms.TemplateBinding.class); assertEquals(1, form.getJieguoSuoKuaizhao().size()); assertEquals("result-binding", form.getJieguoSuoKuaizhao().get(0).getBindingId()); assertNoMethod(ElnFormulaForms.ActivateFormulaBinding.class, "getJieguoSuoKuaizhao"); assertNoMethod(ElnFormulaForms.RemoveMappingConfirm.class, "getJieguoSuoKuaizhao"); assertNoMethod(ElnFormulaForms.UpgradeMappingConfirm.class, "getJieguoSuoKuaizhao"); } @Test public void strictlyAcceptsTicketPrefixOnly() { AtomicInteger authorizations = new AtomicInteger(); ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController(ticket -> { authorizations.incrementAndGet(); return authorization(); }, new StubBindings()); assertEquals(401, controller.context(null).getStatusCodeValue()); assertEquals(401, controller.context("").getStatusCodeValue()); assertEquals(401, controller.context("Bearer token").getStatusCodeValue()); assertEquals(401, controller.context("ticket token").getStatusCodeValue()); assertEquals(401, controller.context("Ticket ").getStatusCodeValue()); assertEquals(0, authorizations.get()); } @Test public void keepsAuthorizedTenantThroughServiceAndPassesSameAuthorization() { TemplateAnnotationAuthorization expected = authorization(); AtomicReference parsedTicket = new AtomicReference<>(); StubBindings service = new StubBindings(); service.onContext = auth -> { assertSame(expected, auth); assertEquals("tenant-1", TenantHolder.getLocalTenantCache().getEnCode()); }; ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController(ticket -> { parsedTicket.set(ticket); TenantVO tenant = new TenantVO(); tenant.setEnCode("tenant-1"); TenantHolder.setLocalTenantCache(tenant); return expected; }, service); ResponseEntity> response = controller.context("Ticket opaque-ticket"); assertEquals(200, response.getStatusCodeValue()); assertEquals("opaque-ticket", parsedTicket.get()); assertSame(expected, service.authorization); assertNull(TenantHolder.getLocalTenantCache()); } @Test public void mapsStructuredAuthorizationAndOperationFailuresAndAlwaysClearsTenant() { assertFailure(401, authorizationFailure(401, "invalid"), false); assertFailure(403, authorizationFailure(403, "forbidden"), false); assertFailure(409, new ElnTemplateOperationException(409, "changed"), true); assertFailure(422, new ElnTemplateOperationException(422, "invalid field"), true); assertFailure(422, new DataException("invalid control"), true); } @Test public void missingRequestBodiesReturn422ThroughUnifiedExecutor() { ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController( ticket -> authorization(), new StubBindings()); String header = "Ticket opaque"; assert422(controller.suggestCode(header, null)); assert422(controller.reservePrivateField(header, null)); assert422(controller.reserveBinding(header, null)); assert422(controller.reserveReference(header, "binding-1", null)); assert422(controller.changeCheckboxGroupRule(header, "result_choice", null)); assert422(controller.activate(header, "binding-1", null)); assert422(controller.cancel(header, "binding-1", null)); assert422(controller.confirmRepair(header, "binding-1", null)); assert422(controller.checkUpgrade(header, "binding-1", null)); assert422(controller.confirmUpgrade(header, "binding-1", null)); assert422(controller.confirmRemove(header, "binding-1", null)); assert422(controller.confirmPromote(header, "binding-1", null)); assert422(controller.confirmVersionCopy(header, null)); } @Test public void suggestsCodeThroughTicketProtectedEndpoint() { ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController( ticket -> authorization(), new StubBindings()); ElnTemplateForms.CodeSuggestion form = new ElnTemplateForms.CodeSuggestion(); form.setText("检验结果"); ResponseEntity> response = controller.suggestCode("Ticket opaque", form); assertEquals(200, response.getStatusCodeValue()); assertEquals("jianyan_jieguo", response.getBody().getData().getCode()); form.setText(" "); assert422(controller.suggestCode("Ticket opaque", form)); assertEquals(401, controller.suggestCode("Bearer opaque", form).getStatusCodeValue()); } @Test public void clearsTenantWhenResponseWrappingFailsAfterServiceReturns() { StubBindings service = new StubBindings(); ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController( ticket -> tenantAuthorization(), service) { @Override protected ResponseEntity> success(T data) { throw new IllegalStateException("response failure"); } }; try { controller.context("Ticket opaque"); fail("response wrapping should fail"); } catch (IllegalStateException expected) { assertEquals("response failure", expected.getMessage()); } assertSame(service.authorization, service.authorizationDuringCall); assertNull(TenantHolder.getLocalTenantCache()); } @Test public void formulaContextUsesTicketAuthorizationAndReturnsDedicatedContext() { StubFormulaBindings formulas = new StubFormulaBindings(); ElnTemplateAnnotationController controller = new ElnTemplateAnnotationController( ticket -> authorization(), new StubBindings(), formulas); ResponseEntity> response = controller.formulaContext("Ticket opaque"); assertEquals(200, response.getStatusCodeValue()); assertSame(formulas.context, response.getBody().getData()); assertSame(formulas.authorization, formulas.authorizationDuringCall); } private void assertFailure(int status, RuntimeException failure, boolean serviceFailure) { StubBindings service = new StubBindings(); ElnTemplateAnnotationController controller; if (serviceFailure) { service.failure = failure; controller = new ElnTemplateAnnotationController(ticket -> tenantAuthorization(), service); } else { controller = new ElnTemplateAnnotationController(ticket -> { TenantVO tenant = new TenantVO(); tenant.setEnCode("tenant-1"); TenantHolder.setLocalTenantCache(tenant); throw failure; }, service); } ResponseEntity> response = controller.context("Ticket opaque"); assertEquals(status, response.getStatusCodeValue()); assertEquals(Integer.valueOf(status), response.getBody().getCode()); assertNull(TenantHolder.getLocalTenantCache()); } private static TemplateAnnotationAuthorization tenantAuthorization() { TenantVO tenant = new TenantVO(); tenant.setEnCode("tenant-1"); TenantHolder.setLocalTenantCache(tenant); return authorization(); } private static TemplateAnnotationAuthorization authorization() { try { Constructor constructor = TemplateAnnotationAuthorization.class.getDeclaredConstructor( String.class, String.class, String.class, String.class); constructor.setAccessible(true); return constructor.newInstance("tenant-1", "user-1", "template-1", "file-1"); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } private static RuntimeException authorizationFailure(int status, String message) { try { Constructor constructor = Class.forName( "jnpf.elnService.template.TemplateAnnotationAuthorizer$AnnotationAuthorizationException") .getDeclaredConstructor(int.class, String.class); constructor.setAccessible(true); return (RuntimeException) constructor.newInstance(status, message); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } private static void assertGet(String name, String path, Class... types) throws Exception { Method method = ElnTemplateAnnotationController.class.getMethod(name, types); assertEquals(path, first(method.getAnnotation(GetMapping.class).value())); } private static void assertPost(String name, String path, Class... types) throws Exception { Method method = ElnTemplateAnnotationController.class.getMethod(name, types); assertEquals(path, first(method.getAnnotation(PostMapping.class).value())); } private static void assertOptionalBody(String name, Class... types) throws Exception { Method method = ElnTemplateAnnotationController.class.getMethod(name, types); Annotation[] annotations = method.getParameterAnnotations()[types.length - 1]; for (Annotation annotation : annotations) { if (annotation instanceof RequestBody) { assertFalse(((RequestBody) annotation).required()); return; } } fail("missing @RequestBody on " + name); } private static void assertNoMethod(Class type, String name) { for (Method method : type.getMethods()) { if (name.equals(method.getName())) fail(type.getSimpleName() + " must not accept reserve lock snapshots"); } } private static void assert422(ResponseEntity> response) { assertEquals(422, response.getStatusCodeValue()); assertEquals(Integer.valueOf(422), response.getBody().getCode()); } private static String first(String[] paths) { return paths.length == 0 ? "" : paths[0]; } private interface ContextCallback { void accept(TemplateAnnotationAuthorization authorization); } private static class StubBindings implements ElnTemplateBindingService { private TemplateAnnotationAuthorization authorization; private TemplateAnnotationAuthorization authorizationDuringCall; private ContextCallback onContext; private RuntimeException failure; @Override public ElnTemplateVos.AnnotationContext context(TemplateAnnotationAuthorization a) { authorization = a; authorizationDuringCall = a; if (onContext != null) onContext.accept(a); if (failure != null) throw failure; return new ElnTemplateVos.AnnotationContext(); } @Override public ElnTemplateVos.Binding reservePrimary(TemplateAnnotationAuthorization a, ElnTemplateForms.ReserveBinding f) { authorization = a; return new ElnTemplateVos.Binding(); } @Override public ElnTemplateVos.Binding reserveReference(TemplateAnnotationAuthorization a, String id, String op) { authorization = a; return new ElnTemplateVos.Binding(); } @Override public ElnTemplateVos.Binding changeCheckboxGroup(TemplateAnnotationAuthorization a, String id, ElnTemplateForms.ChangeCheckboxGroup f) { authorization = a; return new ElnTemplateVos.Binding(); } @Override public ElnTemplateVos.Binding createPrivateFieldAndReserve(TemplateAnnotationAuthorization a, ElnTemplateForms.PrivateField f) { authorization = a; return new ElnTemplateVos.Binding(); } @Override public void activate(TemplateAnnotationAuthorization a, String id, String op) { authorization = a; } @Override public void cancel(TemplateAnnotationAuthorization a, String id, String op, String reason) { authorization = a; } @Override public ElnTemplateVos.RepairContext repairContext(TemplateAnnotationAuthorization a, String id) { authorization = a; return new ElnTemplateVos.RepairContext(); } @Override public void confirmRepair(TemplateAnnotationAuthorization a, String id, ElnTemplateForms.RepairConfirm f) { authorization = a; } @Override public ElnTemplateVos.ChangeCheck checkUpgrade(TemplateAnnotationAuthorization a, String id, String target) { authorization = a; return new ElnTemplateVos.ChangeCheck(); } @Override public void confirmUpgrade(TemplateAnnotationAuthorization a, String id, ElnTemplateForms.UpgradeConfirm f) { authorization = a; } @Override public ElnTemplateVos.ChangeCheck checkRemove(TemplateAnnotationAuthorization a, String id) { authorization = a; return new ElnTemplateVos.ChangeCheck(); } @Override public void confirmRemove(TemplateAnnotationAuthorization a, String id, ElnTemplateForms.RemoveConfirm f) { authorization = a; } @Override public void confirmRemoveMissing(String id, ElnTemplateForms.RemoveMissingConfirm f) { } @Override public ElnTemplateVos.ChangeCheck checkPromoteReference(TemplateAnnotationAuthorization a, String id) { authorization = a; return new ElnTemplateVos.ChangeCheck(); } @Override public void confirmPromoteReference(TemplateAnnotationAuthorization a, String id, ElnTemplateForms.PromoteConfirm f) { authorization = a; } @Override public void confirmVersionCopy(TemplateAnnotationAuthorization a, ElnTemplateForms.VersionCopyConfirm f) { authorization = a; } } private static class StubFormulaBindings implements ElnTemplateFormulaBindingService { private final ElnFormulaVos.TemplateMappingContext context = new ElnFormulaVos.TemplateMappingContext(); private TemplateAnnotationAuthorization authorization; private TemplateAnnotationAuthorization authorizationDuringCall; @Override public java.util.List list(TemplateAnnotationAuthorization a) { authorization = a; return java.util.Collections.emptyList(); } @Override public ElnFormulaVos.TemplateMappingContext context(TemplateAnnotationAuthorization a) { authorization = a; authorizationDuringCall = a; return context; } @Override public ElnFormulaVos.TemplateBinding reserve(TemplateAnnotationAuthorization a, ElnFormulaForms.TemplateBinding f) { authorization = a; return new ElnFormulaVos.TemplateBinding(); } @Override public void activate(TemplateAnnotationAuthorization a, String id, ElnFormulaForms.ActivateFormulaBinding f) { authorization = a; } @Override public void cancel(TemplateAnnotationAuthorization a, String id, ElnFormulaForms.CancelFormulaBinding f) { authorization = a; } @Override public ElnFormulaVos.MappingCheck checkUpgrade(TemplateAnnotationAuthorization a, String id, ElnFormulaForms.TemplateBinding f) { authorization = a; return new ElnFormulaVos.MappingCheck(); } @Override public void confirmUpgrade(TemplateAnnotationAuthorization a, String id, ElnFormulaForms.UpgradeMappingConfirm f) { authorization = a; } @Override public ElnFormulaVos.MappingCheck checkRemove(TemplateAnnotationAuthorization a, String id) { authorization = a; return new ElnFormulaVos.MappingCheck(); } @Override public void confirmRemove(TemplateAnnotationAuthorization a, String id, ElnFormulaForms.RemoveMappingConfirm f) { authorization = a; } @Override public void confirmVersionCopy(TemplateAnnotationAuthorization a, ElnFormulaForms.FormulaVersionCopyConfirm f) { authorization = a; } @Override public jnpf.elnService.formula.FormulaDependencyGraph.Result validateGraph(String tenantId, String templateId) { return null; } } }