/******************************************************************************* * Copyright (c) 2014-2016 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.properties.editor.test; import static org.springframework.ide.eclipse.boot.properties.editor.reconciling.SpringPropertiesProblemType.PROP_DUPLICATE_KEY; import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.assertContains; import java.time.Duration; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.springframework.ide.eclipse.boot.properties.editor.SpringPropertiesCompletionEngine; import org.springframework.ide.eclipse.boot.properties.editor.StsConfigMetadataRepositoryJsonLoader; import org.springframework.ide.eclipse.boot.properties.editor.metadata.CachingValueProvider; import org.springframework.ide.eclipse.boot.properties.editor.reconciling.SpringPropertyProblem; import org.springframework.ide.eclipse.boot.properties.editor.test.ApplicationYamlEditorTestHarness.YamlEditor; import org.springframework.ide.eclipse.boot.properties.editor.util.AptUtils; import org.springframework.ide.eclipse.boot.util.JavaProjectUtil; import org.springframework.ide.eclipse.editor.support.reconcile.ReconcileProblem; import com.google.common.collect.ImmutableList; public class SpringPropertiesEditorTests extends SpringPropertiesEditorTestHarness { public void testServerPortCompletion() throws Exception { data("server.port", INTEGER, 8080, "Port where server listens for http."); if (SpringPropertiesCompletionEngine.DEFAULT_VALUE_INCLUDED) { assertCompletion("ser<*>", "server.port=8080<*>"); } else { assertCompletion("ser<*>", "server.port=<*>"); } assertCompletionDisplayString("ser<*>", "server.port : int"); } public void testLoggingLevelCompletion() throws Exception { data("logging.level", "java.util.Map<java.lang.String,java.lang.Object>", null, "Logging level per package."); assertCompletion("lolev<*>","logging.level.<*>"); } public void testListCompletion() throws Exception { data("foo.bars", "java.util.List<java.lang.String>", null, "List of bars in foo."); assertCompletion("foba<*>","foo.bars=<*>"); } public void testInetAddresCompletion() throws Exception { defaultTestData(); assertCompletion("server.add<*>", "server.address=<*>"); } public void testStringArrayCompletion() throws Exception { data("spring.freemarker.view-names", "java.lang.String[]", null, "White list of view names that can be resolved."); data("some.defaulted.array", "java.lang.String[]", new String[] {"a", "b", "c"} , "Stuff."); assertCompletion("spring.freemarker.vn<*>", "spring.freemarker.view-names=<*>"); if (SpringPropertiesCompletionEngine.DEFAULT_VALUE_INCLUDED) { assertCompletion("some.d.a<*>", "some.defaulted.array=a,b,c<*>"); } else { assertCompletion("some.d.a<*>", "some.defaulted.array=<*>"); } } public void testEmptyPrefixProposalsSortedAlpabetically() throws Exception { defaultTestData(); MockEditor editor = newEditor(""); ICompletionProposal[] completions = getCompletions(editor); assertTrue(completions.length>100); //should be many proposals String previous = null; for (ICompletionProposal c : completions) { String current = c.getDisplayString(); if (previous!=null) { assertTrue("Incorrect order: \n "+previous+"\n "+current, previous.compareTo(current)<=0); } previous = current; } } public void testValueCompletion() throws Exception { defaultTestData(); assertCompletionsVariations("liquibase.enabled=<*>", "liquibase.enabled=false<*>", "liquibase.enabled=true<*>" ); assertCompletionsVariations("liquibase.enabled:<*>", "liquibase.enabled:false<*>", "liquibase.enabled:true<*>" ); assertCompletionsVariations("liquibase.enabled = <*>", "liquibase.enabled = false<*>", "liquibase.enabled = true<*>" ); assertCompletionsVariations("liquibase.enabled <*>", "liquibase.enabled false<*>", "liquibase.enabled true<*>" ); assertCompletionsVariations("liquibase.enabled=f<*>", "liquibase.enabled=false<*>" ); assertCompletionsVariations("liquibase.enabled=t<*>", "liquibase.enabled=true<*>" ); assertCompletionsVariations("liquibase.enabled:f<*>", "liquibase.enabled:false<*>" ); assertCompletionsVariations("liquibase.enabled:t<*>", "liquibase.enabled:true<*>" ); assertCompletionsVariations("liquibase.enabled = f<*>", "liquibase.enabled = false<*>" ); assertCompletionsVariations("liquibase.enabled = t<*>", "liquibase.enabled = true<*>" ); assertCompletionsVariations("liquibase.enabled t<*>", "liquibase.enabled true<*>" ); //one more... for special char like '-' in the name assertCompletionsVariations("liquibase.check-change-log-location=t<*>", "liquibase.check-change-log-location=true<*>" ); } public void testHoverInfos() throws Exception { defaultTestData(); MockEditor editor = newEditor( "#foo\n" + "# bar\n" + "server.port=8080\n" + "logging.level.com.acme=INFO\n" ); //Case 1: an 'exact' match of the property is in the hover region assertHoverText(editor, "server.", "<b>server.port</b>" ); //Case 2: an object/map property has extra text after the property name assertHoverText(editor, "logging.", "<b>logging.level</b>"); } public void testHoverInfosWithSpaces() throws Exception { defaultTestData(); MockEditor editor = newEditor( "#foo\n" + "# bar\n"+ "\n" + " server.port = 8080\n" + " logging.level.com.acme = INFO\n" ); //Case 1: an 'exact' match of the property is in the hover region assertHoverText(editor, "server.", "<b>server.port</b>" ); //Case 2: an object/map property has extra text after the property name assertHoverText(editor, "logging.", "<b>logging.level</b>"); } public void testHoverLongAndShort() throws Exception { data("server.port", INTEGER, 8080, "Port where server listens for http."); data("server.port.fancy", BOOLEAN, 8080, "Whether the port is fancy."); MockEditor editor = newEditor( "server.port=8080\n" + "server.port.fancy=true\n" ); assertHoverText(editor, "server.", "<b>server.port</b>"); assertHoverText(editor, "port.fa", "<b>server.port.fancy</b>"); } public void testPredefinedProject() throws Exception { IProject p = createPredefinedMavenProject("demo"); IType type = JavaCore.create(p).findType("demo.DemoApplication"); assertNotNull(type); } public void testEnableApt() throws Throwable { IProject p = createPredefinedMavenProject("demo-live-metadata"); IJavaProject jp = JavaCore.create(p); //Check some assumptions about the initial state of the test project (if these checks fail then // the test may be 'vacuous' since the things we are testing for already exist beforehand. assertTrue(AptUtils.isAptEnabled(jp)); IFile metadataFile = JavaProjectUtil.getOutputFile(jp, StsConfigMetadataRepositoryJsonLoader.PROJECT_META_DATA_LOCATIONS[0]); assertTrue(metadataFile.exists()); assertContains("\"name\": \"foo.counter\"", getContents(metadataFile)); } public void testHyperlinkTargets() throws Exception { System.out.println(">>> testHyperlinkTargets"); IProject p = createPredefinedMavenProject("demo"); IJavaProject jp = JavaCore.create(p); useProject(jp); MockEditor editor = newEditor( "server.port=888\n" + "spring.datasource.login-timeout=1000\n" + "flyway.init-sqls=a,b,c\n" ); assertLinkTargets(editor, "server", "org.springframework.boot.autoconfigure.web.ServerProperties.setPort(Integer)" ); assertLinkTargets(editor, "data", "org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.hikariDataSource()", "org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.tomcatDataSource()", "org.springframework.boot.autoconfigure.jdbc.DataSourceConfigMetadata.dbcpDataSource()" ); assertLinkTargets(editor, "flyway", "org.springframework.boot.autoconfigure.flyway.FlywayProperties.setInitSqls(List<String>)"); System.out.println("<<< testHyperlinkTargets"); } public void testHyperlinkTargetsLoggingLevel() throws Exception { System.out.println(">>> testHyperlinkTargetsLoggingLevel"); IProject p = createPredefinedMavenProject("demo"); IJavaProject jp = JavaCore.create(p); useProject(jp); MockEditor editor = newEditor( "logging.level.com.acme=INFO\n" ); assertLinkTargets(editor, "level", "org.springframework.boot.logging.LoggingApplicationListener" ); System.out.println("<<< testHyperlinkTargetsLoggingLevel"); } public void testReconcile() throws Exception { defaultTestData(); MockEditor editor = newEditor( "server.port=8080\n" + "server.port.extracrap=8080\n" + "logging.level.com.acme=INFO\n" + "logging.snuggem=what?\n" + "bogus.no.good=true\n" ); assertProblems(editor, ".extracrap|Can't use '.' navigation", "snuggem|unknown property", "ogus.no.good|unknown property" ); } public void testReconcilePojoArray() throws Exception { IProject p = createPredefinedMavenProject("demo-list-of-pojo"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Foo")); MockEditor editor = newEditor( "token.bad.guy=problem\n"+ "volder.foo.list[0].name=Kris\n" + "volder.foo.list[0].description=Kris\n" + "volder.foo.list[0].roles[0]=Developer\n"+ "volder.foo.list[0]garbage=Grable\n"+ "volder.foo.list[0].bogus=Bad\n" ); //This is the more ambitious requirement but it is not implemented yet. assertProblems(editor, "token.bad.guy|unknown property", //'name' is ok //'description' is ok "garbage|'.' or '['", "bogus|has no property" ); } public void testPojoArrayCompletions() throws Exception { IProject p = createPredefinedMavenProject("demo-list-of-pojo"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Foo")); assertCompletionsVariations("volder.foo.l<*>", "volder.foo.list[<*>"); assertCompletionsDisplayString("volder.foo.list[0].<*>", "name : String", "description : String", "roles : List<String>"); assertCompletionsVariations("volder.foo.list[0].na<*>", "volder.foo.list[0].name=<*>" ); assertCompletionsVariations("volder.foo.list[0].d<*>", "volder.foo.list[0].description=<*>" ); assertCompletionsVariations("volder.foo.list[0].rl<*>", "volder.foo.list[0].roles=<*>" ); } public void testReconcileArrayNotation() throws Exception { defaultTestData(); MockEditor editor = newEditor( "borked=bad+\n" + //token problem, to make sure reconciler is working "security.user.role[0]=foo\n" + "security.user.role[${one}]=foo" ); assertProblems(editor, "orked|unknown property" //no other problems ); } public void testReconcileArrayNotationError() throws Exception { defaultTestData(); MockEditor editor = newEditor( "security.user.role[bork]=foo\n" + "security.user.role[1=foo\n" + "security.user.role[1]crap=foo\n" + "server.port[0]=8888\n" + "spring.thymeleaf.view-names[1]=hello" //This is okay now. Boot handles this notation for arrays ); assertProblems(editor, "bork|Integer", "[|matching ']'", "crap|'.' or '['", "[0]|Can't use '[..]'" //no other problems ); } public void testRelaxedNameReconciling() throws Exception { data("connection.remote-host", "java.lang.String", "service.net", null); data("foo-bar.name", "java.lang.String", null, null); MockEditor editor = newEditor( "bork=foo\n" + "connection.remote-host=alternate.net\n" + "connection.remoteHost=alternate.net\n" + "foo-bar.name=Charlie\n" + "fooBar.name=Charlie\n" ); assertProblems(editor, "bork|unknown property" //no other problems ); } public void testRelaxedNameReconcilingErrors() throws Exception { //Tricky with relaxec names: the error positions have to be moved // around because the relaxed names aren't same length as the // canonical ids. data("foo-bar-zor.enabled", "java.lang.Boolean", null, null); MockEditor editor = newEditor( "fooBarZor.enabled=notBoolean\n" + "fooBarZor.enabled.subprop=true\n" ); assertProblems(editor, "notBoolean|boolean", ".subprop|Can't use '.' navigation" ); } public void testRelaxedNameContentAssist() throws Exception { data("foo-bar-zor.enabled", "java.lang.Boolean", null, null); assertCompletion("fooBar<*>", "foo-bar-zor.enabled=<*>"); } public void testReconcileValues() throws Exception { defaultTestData(); MockEditor editor = newEditor( "server.port=badPort\n" + "liquibase.enabled=nuggels" ); assertProblems(editor, "badPort|'int'", "nuggels|'boolean'" ); } public void testNoReconcileInterpolatedValues() throws Exception { defaultTestData(); MockEditor editor = newEditor( "server.port=${port}\n" + "liquibase.enabled=nuggels" ); assertProblems(editor, //no problem should be reported for ${port} "nuggels|'boolean'" ); } public void testReconcileValuesWithSpaces() throws Exception { ignoreProblem(PROP_DUPLICATE_KEY); //ignore deliberate abuse of dups defaultTestData(); MockEditor editor = newEditor( "server.port = badPort\n" + "liquibase.enabled nuggels \n" + "liquibase.enabled : snikkers" ); assertProblems(editor, "badPort|'int'", "nuggels|'boolean'", "snikkers|'boolean'" ); } public void testReconcileWithExtraSpaces() throws Exception { defaultTestData(); //Same test as previous but with extra spaces to make things more confusing MockEditor editor = newEditor( " server.port = 8080 \n" + "\n" + " server.port.extracrap = 8080\n" + " logging.level.com.acme : INFO\n" + "logging.snuggem = what?\n" + "bogus.no.good= true\n" ); assertProblems(editor, ".extracrap|Can't use '.' navigation", "snuggem|unknown property", "ogus.no.good|unknown property" ); } public void testEnumPropertyCompletionInsideCommaSeparateList() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); data("foo.colors", "java.util.List<demo.Color>", null, "A foonky list"); //Completion requested right after '=' sign: assertCompletionsDisplayString("foo.colors=<*>", "red", "green", "blue"); assertCompletionWithLabel("foo.colors=<*>", "red", "foo.colors=red<*>"); assertCompletion("foo.colors=R<*>", "foo.colors=RED<*>"); assertCompletion("foo.colors=g<*>", "foo.colors=green<*>"); assertCompletion("foo.colors=B<*>", "foo.colors=BLUE<*>"); //Completion requested after ',' assertCompletionsDisplayString("foo.colors=red,<*>", "red", "green", "blue"); assertCompletionWithLabel("foo.colors=red,<*>", "green", "foo.colors=red,green<*>"); assertCompletion("foo.colors=RED,R<*>", "foo.colors=RED,RED<*>"); assertCompletion("foo.colors=RED,G<*>", "foo.colors=RED,GREEN<*>"); assertCompletion("foo.colors=RED,B<*>", "foo.colors=RED,BLUE<*>"); } public void testEnumPropertyCompletion() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); data("foo.color", "demo.Color", null, "A foonky colour"); assertCompletion("foo.c<*>", "foo.color=<*>"); //Should add the '=' because enums are 'simple' values. assertCompletion("foo.color=R<*>", "foo.color=RED<*>"); assertCompletion("foo.color=G<*>", "foo.color=GREEN<*>"); assertCompletion("foo.color=B<*>", "foo.color=BLUE<*>"); assertCompletionsDisplayString("foo.color=<*>", "red", "green", "blue" ); } public void testEnumPropertyReconciling() throws Exception { ignoreProblem(PROP_DUPLICATE_KEY); //ignore deliberate abuse of dups IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); data("foo.color", "demo.Color", null, "A foonky colour"); MockEditor editor = newEditor( "foo.color=BLUE\n"+ "foo.color=RED\n"+ "foo.color=GREEN\n"+ "foo.color.bad=BLUE\n"+ "foo.color=Bogus\n" ); assertProblems(editor, ".bad|Can't use '.' navigation", "Bogus|Color" ); } public void testEnumMapValueCompletion() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertCompletionsVariations("foo.nam<*>", "foo.name-colors.<*>", "foo.color-names.<*>" ); assertCompletionsDisplayString("foo.name-colors.something=<*>", "red", "green", "blue" ); assertCompletionsVariations("foo.name-colors.something=G<*>", "foo.name-colors.something=GREEN<*>"); } public void testEnumMapValueReconciling() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); data("foo.name-colors", "java.util.Map<java.lang.String,demo.Color>", null, "Map with colors in its values"); assertNotNull(jp.findType("demo.Color")); MockEditor editor = newEditor( "foo.name-colors.jacket=BLUE\n" + "foo.name-colors.hat=RED\n" + "foo.name-colors.pants=GREEN\n" + "foo.name-colors.wrong=NOT_A_COLOR\n" ); assertProblems(editor, "NOT_A_COLOR|Color" ); } public void testEnumMapKeyCompletion() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); data("foo.color-names", "java.util.Map<demo.Color,java.lang.String>", null, "Map with colors in its keys"); data("foo.color-data", "java.util.Map<demo.Color,demo.ColorData>", null, "Map with colors in its keys, and pojo in values"); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); //Map Enum -> String: assertCompletionsVariations("foo.colnam<*>", "foo.color-names.<*>"); assertCompletionsVariations("foo.color-names.<*>", "foo.color-names.blue=<*>", "foo.color-names.green=<*>", "foo.color-names.red=<*>" ); assertCompletionsDisplayString("foo.color-names.<*>", "red : String", "green : String", "blue : String" ); assertCompletionsVariations("foo.color-names.B<*>", "foo.color-names.BLUE=<*>" ); //Map Enum -> Pojo: assertCompletionsVariations("foo.coldat<*>", "foo.color-data.<*>"); assertCompletionsVariations("foo.color-data.<*>", "foo.color-data.blue.<*>", "foo.color-data.green.<*>", "foo.color-data.red.<*>" ); assertCompletionsVariations("foo.color-data.B<*>", "foo.color-data.BLUE.<*>" ); assertCompletionsDisplayString("foo.color-data.<*>", "blue : demo.ColorData", "green : demo.ColorData", "red : demo.ColorData" ); } public void testEnumMapKeyReconciling() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); MockEditor editor = newEditor( "foo.color-names.RED=Rood\n"+ "foo.color-names.GREEN=Groen\n"+ "foo.color-names.BLUE=Blauw\n" + "foo.color-names.NOT_A_COLOR=Wrong\n" + "foo.color-names.BLUE.bad=Blauw\n" ); assertProblems(editor, "NOT_A_COLOR|Color", "BLUE.bad|Color" //because value type is not dotable the dots will be taken to be part of map key ); } public void testPojoCompletions() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); assertCompletion("foo.dat<*>", "foo.data.<*>"); assertCompletionsDisplayString("foo.data.", "wavelen : double", "name : String", "next : demo.Color[RED, GREEN, BLUE]", "nested : demo.ColorData", "children : List<demo.ColorData>", "mapped-children : Map<String, demo.ColorData>", "color-children : Map<demo.Color[RED, GREEN, BLUE], demo.ColorData>", "tags : List<String>", "funky : boolean" ); assertCompletionsVariations("foo.data.wav<*>", "foo.data.wavelen=<*>"); assertCompletionsVariations("foo.data.nam<*>", "foo.data.name=<*>"); assertCompletionsVariations("foo.data.nex<*>", "foo.data.next=<*>"); assertCompletionsVariations("foo.data.nes<*>", "foo.data.nested.<*>"); assertCompletionsVariations("foo.data.chi<*>", "foo.data.children[<*>", "foo.data.color-children.<*>", //fuzzy "foo.data.mapped-children.<*>" //fuzzy ); assertCompletionsVariations("foo.data.tag<*>", "foo.data.tags=<*>"); assertCompletionsVariations("foo.data.map<*>", "foo.data.mapped-children.<*>"); assertCompletionsVariations("foo.data.col<*>", "foo.data.color-children.<*>"); } public void testPojoReconciling() throws Exception { ignoreProblem(PROP_DUPLICATE_KEY); //ignore deliberate abuse of dups IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); MockEditor editor = newEditor( "foo.data.bogus=Something\n" + "foo.data.wavelen=3.0\n" + "foo.data.wavelen=not a double\n" + "foo.data.wavelen.more=3.0\n" + "foo.data.wavelen[0]=3.0\n" ); assertProblems(editor, "bogus|no property", "not a double|'double'", ".more|Can't use '.' navigation", "[0]|Can't use '[..]' navigation" ); } public void testListOfAtomicCompletions() throws Exception { data("foo.slist", "java.util.List<java.lang.String>", null, "list of strings"); data("foo.ulist", "java.util.List<Unknown>", null, "list of strings"); data("foo.dlist", "java.util.List<java.lang.Double>", null, "list of doubles"); assertCompletionsVariations("foo.u<*>", "foo.ulist[<*>"); assertCompletionsVariations("foo.d<*>", "foo.dlist=<*>"); assertCompletionsVariations("foo.sl<*>", "foo.slist=<*>"); } public void testMapKeyDotInterpretation() throws Exception { //Interpretation of '.' changes depending on the domain type (i.e. when domain type is //is a simple type got which '.' navigation is invalid then the '.' is 'eaten' by the key. IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); data("atommap", "java.util.Map<java.lang.String,java.lang.Integer>", null, "map of atomic data"); data("objectmap", "java.util.Map<java.lang.String,java.lang.Object>", null, "map of atomic object (recursive map)"); data("enummap", "java.util.Map<java.lang.String,demo.Color>", null, "map of enums"); data("pojomap", "java.util.Map<java.lang.String,demo.ColorData>", null, "map of pojos"); MockEditor editor = newEditor( "atommap.something.with.dots=Vaporize\n" + "atommap.something.with.bracket[0]=Brackelate\n" + "objectmap.other.with.dots=Objectify\n" + "enummap.more.dots=Enumerate\n" + "pojomap.do.some.dots=Pojodot\n" + "pojomap.bracket.and.dots[1]=lala\n" + "pojomap.zozo[2]=lala\n" ); assertProblems(editor, "Vaporize|'int'", "[0]|Can't use '[..]'", //objectmap okay "Enumerate|Color", "some|no property", "and|no property", "[2]|Can't use '[..]'" ); assertCompletionsVariations("enummap.more.dots=R<*>", "enummap.more.dots=RED<*>", "enummap.more.dots=GREEN<*>" //fuzzy match: G(R)EEN ); } public void testMapKeyDotInterpretationInPojo() throws Exception { //Similar to testMapKeyDotInterpretation but this time maps are not attached to property // directly but via a pojo property IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); assertNotNull(jp.findType("demo.ColorData")); MockEditor editor = newEditor( "foo.color-names.BLUE.dot=Blauw\n"+ "foo.color-data.RED.name=Good\n"+ "foo.color-data.GREEN.bad=Bad\n"+ "foo.color-data.GREEN.wrong[1]=Wrong\n" ); assertProblems(editor, "BLUE.dot|Color", //dot is eaten so this is an error "bad|no property", //dot not eaten so '.bad' is accessing a property "wrong|no property" ); assertCompletionsVariations("foo.color-data.RED.ch<*>", "foo.color-data.RED.children[<*>", "foo.color-data.RED.color-children.<*>", "foo.color-data.RED.mapped-children.<*>" ); } public void testEnumsInLowerCaseReconciling() throws Exception { ignoreProblem(PROP_DUPLICATE_KEY); //ignore deliberate abuse of dups IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.ClothingSize")); data("simple.pants.size", "demo.ClothingSize", null, "The simple pant's size"); MockEditor editor = newEditor( "simple.pants.size=NOT_A_SIZE\n"+ "simple.pants.size=EXTRA_SMALL\n"+ "simple.pants.size=extra-small\n"+ "simple.pants.size=small\n"+ "simple.pants.size=SMALL\n" ); assertProblems(editor, "NOT_A_SIZE|ClothingSize" ); editor = newEditor( "foo.color-names.red=Rood\n"+ "foo.color-names.green=Groen\n"+ "foo.color-names.blue=Blauw\n" + "foo.color-names.not-a-color=Wrong\n" + "foo.color-names.blue.bad=Blauw\n" ); assertProblems(editor, "not-a-color|Color", "blue.bad|Color" //because value type is not dotable the dots will be taken to be part of map key ); editor = newEditor( "foo.color-data.red.next=green\n" + "foo.color-data.red.next=not a color\n" + "foo.color-data.red.bogus=green\n" + "foo.color-data.red.name=Rood\n" ); assertProblems(editor, "not a color|Color", "bogus|no property" ); } public void testEnumsInLowerCaseContentAssist() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.ClothingSize")); data("simple.pants.size", "demo.ClothingSize", null, "The simple pant's size"); assertCompletionsVariations("simple.pants.size=S<*>", "simple.pants.size=SMALL<*>", "simple.pants.size=EXTRA_SMALL<*>" ); assertCompletionsVariations("simple.pants.size=s<*>", "simple.pants.size=small<*>", "simple.pants.size=extra-small<*>" ); assertCompletionsVariations("simple.pants.size=ex<*>", "simple.pants.size=extra-large<*>", "simple.pants.size=extra-small<*>" ); assertCompletionsVariations("simple.pants.size=EX<*>", "simple.pants.size=EXTRA_LARGE<*>", "simple.pants.size=EXTRA_SMALL<*>" ); assertCompletionsDisplayString("foo.color=<*>", "red", "green", "blue"); assertCompletionsVariations("foo.color-data.R<*>", "foo.color-data.RED.<*>", "foo.color-data.GREEN.<*>" ); assertCompletionsVariations("foo.color-data.r<*>", "foo.color-data.red.<*>", "foo.color-data.green.<*>" ); assertCompletionsVariations("foo.color-data.<*>", "foo.color-data.blue.<*>", "foo.color-data.green.<*>", "foo.color-data.red.<*>" ); assertCompletionsVariations("foo.color-data.red.na<*>", "foo.color-data.red.name=<*>"); } public void testNavigationProposalAfterRelaxedPropertyName() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertCompletionsVariations("foo.colorData.b<*>", "foo.colorData.blue.<*>"); assertCompletionsVariations("foo.colorData.red.na<*>", "foo.colorData.red.name=<*>"); } public void testValueProposalAssignedToRelaxedPropertyName() throws Exception { IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); data("relaxed-color", "demo.Color", null, "A soothing color"); assertCompletion("relaxed-color=b<*>", "relaxed-color=blue<*>"); assertCompletion("relaxedColor=b<*>", "relaxedColor=blue<*>"); } public void testReconcileDeprecatedProperty() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); MockEditor editor = newEditor( "# a comment\n"+ "error.path=foo\n" ); deprecate("error.path", "server.error.path", null); assertProblems(editor, "error.path|Deprecated: Use 'server.error.path'" //no other problems ); deprecate("error.path", "server.error.path", "This is old."); assertProblems(editor, "error.path|Deprecated: Use 'server.error.path' instead. Reason: This is old." //no other problems ); deprecate("error.path", null, "This is old."); assertProblems(editor, "error.path|Deprecated: This is old." //no other problems ); deprecate("error.path", null, null); assertProblems(editor, "error.path|Deprecated!" //no other problems ); } public void testDeprecatedPropertyCompletion() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); data("server.error.path", "java.lang.String", null, "Path of the error controller."); deprecate("error.path", "server.error.path", "This is old."); assertCompletionsDisplayString("error.pa<*>", "server.error.path : String", // should be first because it is not deprecated, even though it is not as good a pattern match "error.path : String" ); //TODO: could we check that 'deprecated' completions are formatted with 'strikethrough font? assertStyledCompletions("error.pa<*>", StyledStringMatcher.plainFont("server.error.path : String"), StyledStringMatcher.strikeout("error.path") ); } public void testDeprecatedPropertyHoverInfo() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); MockEditor editor = newEditor( "# a comment\n"+ "error.path=foo\n" ); deprecate("error.path", "server.error.path", null); assertHoverText(editor, "path", "<s>error.path</s> -> server.error.path"); assertHoverText(editor, "path", "<b>Deprecated!</b>"); deprecate("error.path", "server.error.path", "This is old."); assertHoverText(editor, "path", "<s>error.path</s> -> server.error.path"); assertHoverText(editor, "path", "<b>Deprecated: </b>This is old"); deprecate("error.path", null, "This is old."); assertHoverText(editor, "path", "<b>Deprecated: </b>This is old"); deprecate("error.path", null, null); assertHoverText(editor, "path", "<b>Deprecated!</b>"); } public void testDeprecatedPropertyQuickfix() throws Exception { data("error.path", "java.lang.String", null, "Path of the error controller."); deprecate("error.path", "server.error.path", null); MockEditor editor = newEditor( "# a comment\n"+ "error.path=foo\n" ); ReconcileProblem problem = assertProblem(editor, "error.path"); ICompletionProposal fix = assertFirstQuickfix(editor, problem, "Change to 'server.error.path'"); editor.apply(fix); editor.assertText( "# a comment\n"+ "server.error.path<*>=foo\n" ); } public void testDeprecatedBeanPropertyReconcile() throws Exception { IProject jp = createPredefinedMavenProject("demo"); useProject(jp); data("foo", "demo.Deprecater", null, "A Bean with deprecated properties"); MockEditor editor = newEditor( "# comment\n" + "foo.name=Old faithfull\n" + "foo.new-name=New and fancy\n" + "foo.alt-name=alternate\n" ); assertProblems(editor, "name|Deprecated", "alt-name|Property 'alt-name' of type 'demo.Deprecater' is Deprecated: Use 'something.else' instead. Reason: No good anymore" ); } public void testDeprecatedBeanPropertyCompletions() throws Exception { IProject jp = createPredefinedMavenProject("demo"); useProject(jp); data("foo", "demo.Deprecater", null, "A Bean with deprecated properties"); assertStyledCompletions("foo.nam<*>", StyledStringMatcher.plainFont("new-name : String"), StyledStringMatcher.strikeout("name"), StyledStringMatcher.strikeout("alt-name") ); } public void testCharsetCompletions() throws Exception { data("foobar.encoding", "java.nio.charset.Charset", null, "The charset-encoding to use for foobars"); assertCompletions( "foobar.enco<*>" , // ==> "foobar.encoding=<*>" ); assertCompletionWithLabel( "foobar.encoding=UT<*>" , "UTF-8" , "foobar.encoding=UTF-8<*>" ); } public void testLocaleCompletions() throws Exception { data("foobar.locale", "java.util.Locale", null, "Yada yada"); assertCompletions( "foobar.loca<*>" , // ==> "foobar.locale=<*>" ); assertCompletionWithLabel( "foobar.locale=en<*>" , "en_CA" , "foobar.locale=en_CA<*>" ); } public void testPropertyValueHintCompletions() throws Exception { //Test that 'value hints' work when property name is associated with 'value' hints. // via boot metadata. //TODO: this should also work when hints associated with a // map property key // map property value // list property value useProject(createPredefinedMavenProject("boot13")); assertCompletionsDisplayString( "spring.http.converters.preferred-json-mapper=<*>\n" , //=> "gson", "jackson" ); } public void testPropertyListHintCompletions() throws Exception { useProject(createPredefinedMavenProject("boot13")); assertCompletion( "management.health.status.ord<*>" , //=> "management.health.status.order=<*>" ); assertCompletionsDisplayString( "management.health.status.order=<*>" , //=> "DOWN", "OUT_OF_SERVICE", "UNKNOWN", "UP" ); assertCompletionsDisplayString( "management.health.status.order=DOWN,<*>" , //=> "DOWN", "OUT_OF_SERVICE", "UNKNOWN", "UP" ); } public void testPropertyMapValueCompletions() throws Exception { useProject(createPredefinedMavenProject("boot13")); assertCompletionsDisplayString( "logging.level.some: <*>" , // => "trace", "debug", "info", "warn", "error", "fatal", "off" ); assertCompletionsDisplayString( "logging.level.some.package: <*>" , // => "trace", "debug", "info", "warn", "error", "fatal", "off" ); } public void testPropertyMapKeyCompletions() throws Exception { useProject(createPredefinedMavenProject("boot13")); assertCompletionWithLabel( "logging.level.<*>" , //============== "root : String", //=> "logging.level.root=<*>" ); assertCompletionWithLabel( "logging.level.r<*>" , //============== "root : String", //=> "logging.level.root=<*>" ); assertCompletionWithLabel( "logging.level.ot<*>" , //============== "root : String", //=> "logging.level.root=<*>" ); } public void testHandleAsResourceContentAssist() throws Exception { //"name": "my.terms-and-conditions", // "providers": [ // { // "name": "handle-as", // "parameters": { // "target": "org.springframework.core.io.Resource" // } // } // ] data("my.terms-and-conditions", "java.lang.String", null, "Terms and Conditions text file") .provider("handle-as", "target", "org.springframework.core.io.Resource"); assertCompletionsDisplayString( "my.terms-and-conditions=<*>" , // => "classpath:", "classpath*:", "file:", "http://", "https://" ); } public void testHandleAsListContentAssist() throws Exception { data("my.tosses", "String[]", null, "A sequence of coin tosses") .provider("handle-as", "target", "java.lang.Boolean[]"); assertCompletionsDisplayString( "my.tosses[0]=<*>" , // => "true", "false" ); } public void test_STS_3335_reconcile_list_nested_in_Map_of_String() throws Exception { MockEditor editor; useProject(createPredefinedMavenProject("demo-sts-4335")); editor = newEditor( "test-map.test-list-object.color-list[0]=not-a-color\n"+ "test-map.test-list-object.color-list[1]=RED\n"+ "test-map.test-list-object.color-list[2]=GREEN\n" ); assertProblems(editor, "not-a-color|Expecting 'com.wellsfargo.lendingplatform.web.config.Color" ); editor = newEditor( "test-map.test-list-object.string-list[0]=not-a-color\n"+ "test-map.test-list-object.string-list[1]=RED\n"+ "test-map.test-list-object.string-list[2]=GREEN\n" ); assertProblems(editor /*NONE*/); } public void test_STS_3335_completions_list_nested_in_Map_of_String() throws Exception { useProject(createPredefinedMavenProject("demo-sts-4335")); assertCompletions( "test-map.some-string-key.col<*>" , // => "test-map.some-string-key.color-list=<*>" ); assertCompletionsDisplayString( "test-map.some-string-key.color-list[0]=<*>\n" , // => "red", "green", "blue" ); assertCompletionsDisplayString( "test-map.some-string-key.color-list[0]=<*>\n" , // => "red", "green", "blue" ); } public void testSimpleResourceCompletion() throws Exception { CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); useProject(createPredefinedMavenProject("boot13")); data("my.nice.resource", "org.springframework.core.io.Resource", null, "A very nice resource."); assertCompletion( "nicer<*>\n" ,// => "my.nice.resource=<*>\n" ); assertCompletionsDisplayString( "my.nice.resource=<*>\n" , // => "classpath:", "classpath*:", "file:", "http://", "https://" ); } public void testClasspathResourceCompletion() throws Exception { CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); useProject(createPredefinedMavenProject("boot13")); data("my.nice.resource", "org.springframework.core.io.Resource", null, "A very nice resource."); data("my.nice.list", "java.util.List<org.springframework.core.io.Resource>", null, "A nice list of resources."); assertCompletionsDisplayString( "my.nice.resource=classpath:app<*>\n" ,// => "classpath:application.properties", "classpath:application.yml" ); //Test 'list item' context: assertCompletionsDisplayString( "my.nice.list[0]=<*>" ,// => "classpath:", "classpath*:", "file:", "http://", "https://" ); assertCompletionsDisplayString( "my.nice.list[0]=classpath:app<*>\n" ,// => "classpath:application.properties", "classpath:application.yml" ); assertCompletionWithLabel( "my.nice.list[0]=classpath:app<*>\n" ,// ========== "classpath:application.yml" , // => "my.nice.list[0]=classpath:application.yml<*>\n" ); assertCompletionWithLabel( "my.nice.list[0]= classpath:app<*>\n" ,// ========== "classpath:application.yml" , // => "my.nice.list[0]= classpath:application.yml<*>\n" ); assertCompletionWithLabel( "my.nice.list[0]=classpath:<*>\n" ,// ========== "classpath:application.yml" , // => "my.nice.list[0]=classpath:application.yml<*>\n" ); //Test 'raw node' context // do we find resources in sub-folders too? assertCompletionWithLabel( "my.nice.resource=classpath:word<*>\n" ,//=============== "classpath:stuff/wordlist.txt" ,// => "my.nice.resource=classpath:stuff/wordlist.txt<*>\n" ); } public void testClasspathResourceCompletionInCommaList() throws Exception { CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); useProject(createPredefinedMavenProject("boot13")); data("my.nice.list", "java.util.List<org.springframework.core.io.Resource>", null, "A nice list of resources."); data("my.nice.array", "org.springframework.core.io.Resource[]", null, "A nice array of resources."); for (String kind : ImmutableList.of("list", "array")) { assertCompletionWithLabel( "my.nice."+kind+"=classpath:<*>" ,//=========== "classpath:stuff/wordlist.txt" ,//=> "my.nice."+kind+"=classpath:stuff/wordlist.txt<*>" ); assertCompletionsDisplayString( "my.nice."+kind+"=<*>" ,// => "classpath:", "classpath*:", "file:", "http://", "https://" ); assertCompletionWithLabel( "my.nice."+kind+"=classpath:stuff/wordlist.txt,classpath:app<*>" ,//=========== "classpath:application.yml" ,//=> "my.nice."+kind+"=classpath:stuff/wordlist.txt,classpath:application.yml<*>" ); } } public void testClassReferenceCompletion() throws Exception { CachingValueProvider.TIMEOUT = Duration.ofSeconds(20); useProject(createPredefinedMavenProject("boot13_with_mongo")); assertCompletion( "spring.data.mongodb.field-na<*>" , // => "spring.data.mongodb.field-naming-strategy=<*>" ); assertCompletionsDisplayString( "spring.data.mongodb.field-naming-strategy=<*>" , // => "org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy", "org.springframework.data.mapping.model.CamelCaseSplittingFieldNamingStrategy", "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", "org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy" ); assertCompletionWithLabel( "spring.data.mongodb.field-naming-strategy=<*>" , //===== "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy" , //=> "spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy<*>" ); //Test what happens when 'target' type isn't on the classpath: useProject(createPredefinedMavenProject("boot13")); assertCompletionsDisplayString( "spring.data.mongodb.field-naming-strategy=<*>" // => /*NONE*/ ); } public void testClassReferenceInValueLink() throws Exception { MockEditor editor; useProject(createPredefinedMavenProject("boot13_with_mongo")); editor = newEditor( "#stuff\n" + "spring.data.mongodb.field-naming-strategy=org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy\n" + "#more stuff" ); assertLinkTargets(editor, "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy", "org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy"); //Linking should also work for types that aren't valid based on the constraints editor = newEditor( "#stuff\n" + "spring.data.mongodb.field-naming-strategy=java.lang.String\n" + "#more stuff" ); assertLinkTargets(editor, "java.lang.String", "java.lang.String"); } public void testCommaListReconcile() throws Exception { MockEditor editor; IProject p = createPredefinedMavenProject("demo-enum"); IJavaProject jp = JavaCore.create(p); useProject(jp); assertNotNull(jp.findType("demo.Color")); data("my.colors", "java.util.List<demo.Color>", null, "Ooh! nice colors!"); editor = newEditor( "#comment\n" + "my.colors=RED, green, not-a-color , BLUE" ); assertProblems(editor, "not-a-color|demo.Color" ); editor = newEditor( "my.colors=\\\n" + " red,\\\n" + " green,\\\n" + " blue\n" ); assertProblems(editor /*no problems*/); editor = newEditor( "my.colors=\\\n" + " bad,\\\n" + " green,\\\n" + " blue\n" ); assertProblems(editor, "bad|demo.Color"); editor = newEditor( "my.colors=\\\n" + " bad , \\\n" + " green,\\\n" + " blue\n" ); assertProblems(editor, "bad|demo.Color"); editor = newEditor( "my.colors=\\\n" + " red , \\\n" + " green,\\\n" + " bad\n" ); assertProblems(editor, "bad|demo.Color"); editor = newEditor( "my.colors=\\\n" + " red , \\\n" + " green,\\\n" + " bad \n" ); assertProblems(editor, "bad|demo.Color"); editor = newEditor( "my.colors=red,\n" ); assertProblems(editor, ",|demo.Color"); editor = newEditor( "my.colors=red, \n" ); assertProblems(editor, " |demo.Color"); } public void testReconcileDuplicateKey() throws Exception { MockEditor editor; data("some.property", "java.lang.String", null, "yada"); data("some.other.property", "java.lang.String", null, "yada"); editor = newEditor( "#comment\n" + "some.property=stuff\n" + "some.other.property=stuff\n" + "some.property=different stuff\n" ); assertProblems(editor, "some.property|Duplicate", "some.property|Duplicate" ); editor = newEditor( "#comment\n" + "some.property = stuff\n" + "some.other.property=stuff\n" + "some.property: different stuff\n" + "some.other.property=stuff\n" + "some.property: different stuff\n" ); assertProblems(editor, "some.property|Duplicate", "some.other.property|Duplicate", "some.property|Duplicate", "some.other.property|Duplicate", "some.property|Duplicate" ); } public void test_PT_119352965() throws Exception { data("some.property", "java.lang.String", null, "Some property to test stuff") .valueHint("SOMETHING", "A value for something") .valueHint("ALTERNATE", "An alternative value"); data("some.other.property", "java.lang.String", null, "Another property to test stuff"); assertCompletionWithLabel( "some.property=SOMETHING\n" + "<*>" , // =============== "some.other.property : String" , // => "some.property=SOMETHING\n" + "some.other.property=<*>" ); } public void testEnumJavaDocShownInValueContentAssist() throws Exception { useProject(createPredefinedMavenProject("demo-enum")); data("my.background", "demo.Color", null, "Color to use as default background."); assertCompletionWithInfoHover( "my.background=<*>" , // ========== "red" , // ==> "Hot and delicious" ); } public void testEnumJavaDocShownInValueHover() throws Exception { useProject(createPredefinedMavenProject("demo-enum")); data("my.background", "demo.Color", null, "Color to use as default background."); MockEditor editor; editor = newEditor( "my.background: RED" ); editor.assertIsHoverRegion("RED"); editor.assertHoverContains("RED", "Hot and delicious"); editor = newEditor( "my.background=red" ); editor.assertHoverContains("red", "Hot and delicious"); } public void testEnumInValueLink() throws Exception { useProject(createPredefinedMavenProject("demo-enum")); data("my.background", "demo.Color", null, "Color to use as default background."); MockEditor editor; editor = newEditor( "my.background: RED" ); assertLinkTargets(editor, "RED", "demo.Color.RED"); editor = newEditor( "my.background=red" ); assertLinkTargets(editor, "red", "demo.Color.RED"); } // public void testContentAssistAfterRBrack() throws Exception { // //TODO: content assist after ] (auto insert leading '.' if necessary) // } //public void //TODO: relaxed names for navigation operations (i.e. object properties and enum map keys) }