1616import org .neo4j .driver .exceptions .ServiceUnavailableException ;
1717import org .neo4j .driver .exceptions .TransientException ;
1818import org .neo4j .shell .cli .CliArgs ;
19+ import org .neo4j .shell .cli .Format ;
1920import org .neo4j .shell .commands .CommandHelper ;
2021import org .neo4j .shell .exception .CommandException ;
2122import org .neo4j .shell .exception .ExitException ;
3738import static org .mockito .Mockito .mock ;
3839import static org .mockito .Mockito .verify ;
3940import static org .mockito .Mockito .verifyNoMoreInteractions ;
41+ import static org .neo4j .shell .DatabaseManager .DEFAULT_DEFAULT_DB_NAME ;
42+ import static org .neo4j .shell .DatabaseManager .SYSTEM_DB_NAME ;
43+ import static org .neo4j .shell .Main .EXIT_FAILURE ;
44+ import static org .neo4j .shell .Main .EXIT_SUCCESS ;
4045import static org .neo4j .shell .util .Versions .majorVersion ;
4146
4247public class MainIntegrationTest
4348{
49+ private static String USER = "neo4j" ;
50+ private static String PASSWORD = "neo" ;
4451
4552 private static class ShellAndConnection
4653 {
@@ -56,7 +63,7 @@ private static class ShellAndConnection
5663
5764 @ Rule
5865 public final ExpectedException exception = ExpectedException .none ();
59- private String inputString = String .format ( "neo4j%nneo%n" );
66+ private String inputString = String .format ( "%s%n%s%n" , USER , PASSWORD );
6067 private ByteArrayOutputStream baos ;
6168 private ConnectionConfig connectionConfig ;
6269 private CliArgs cliArgs ;
@@ -89,7 +96,7 @@ public void setup() {
8996
9097 private void ensureUser () throws Exception {
9198 if (majorVersion (shell .getServerVersion () ) >= 4 ) {
92- shell .execute (":use " + DatabaseManager . SYSTEM_DB_NAME );
99+ shell .execute (":use " + SYSTEM_DB_NAME );
93100 shell .execute ("CREATE OR REPLACE USER foo SET PASSWORD 'pass';" );
94101 shell .execute ("GRANT ROLE reader TO foo;" );
95102 shell .execute (":use" );
@@ -134,41 +141,23 @@ public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
134141
135142 @ Test
136143 public void promptsOnPasswordChangeRequired () throws Exception {
137- shell .setCommandHelper (new CommandHelper (mock (Logger .class ), Historian .empty , shell ));
138- inputBuffer .put (String .format ("foo%npass%nnewpass%n" ).getBytes ());
139-
140- assertEquals ("" , connectionConfig .username ());
141- assertEquals ("" , connectionConfig .password ());
142-
143- // when
144- main .connectMaybeInteractively (shell , connectionConfig , true , true );
145-
146- // then
147- // should be connected
148- assertTrue (shell .isConnected ());
149- // should have prompted and set the username and password
150- String expectedLoginOutput = format ( "username: neo4j%npassword: ***%n" );
151- assertEquals (expectedLoginOutput , baos .toString ());
152- assertEquals ("neo4j" , connectionConfig .username ());
153- assertEquals ("neo" , connectionConfig .password ());
154-
155- // Create a new user
156- ensureUser ();
157- shell .disconnect ();
144+ int majorVersion = getVersionAndCreateUserWithPasswordChangeRequired ();
158145
159146 connectionConfig = getConnectionConfig (cliArgs );
160147 assertEquals ("" , connectionConfig .username ());
161148 assertEquals ("" , connectionConfig .password ());
162149
163150 // when
151+ inputBuffer .put (String .format ("foo%npass%nnewpass%n" ).getBytes ());
152+ baos .reset ();
164153 main .connectMaybeInteractively (shell , connectionConfig , true , true );
165154
166155 // then
167156 assertTrue (shell .isConnected ());
168- if (majorVersion ( shell . getServerVersion () ) >= 4 ) {
157+ if (majorVersion >= 4 ) {
169158 // should have prompted to change the password
170159 String expectedChangePasswordOutput = format ( "username: foo%npassword: ****%nPassword change required%nnew password: *******%n" );
171- assertEquals (expectedLoginOutput + expectedChangePasswordOutput , baos .toString ());
160+ assertEquals ( expectedChangePasswordOutput , baos .toString ());
172161 assertEquals ("foo" , connectionConfig .username ());
173162 assertEquals ("newpass" , connectionConfig .password ());
174163 assertNull (connectionConfig .newPassword ());
@@ -178,7 +167,7 @@ public void promptsOnPasswordChangeRequired() throws Exception {
178167 } else {
179168 // in 3.x we do not get credentials expired exception on connection, but when we try to access data
180169 String expectedChangePasswordOutput = format ( "username: foo%npassword: ****%n" );
181- assertEquals (expectedLoginOutput + expectedChangePasswordOutput , baos .toString ());
170+ assertEquals ( expectedChangePasswordOutput , baos .toString ());
182171 assertEquals ("foo" , connectionConfig .username ());
183172 assertEquals ("pass" , connectionConfig .password ());
184173
@@ -189,6 +178,61 @@ public void promptsOnPasswordChangeRequired() throws Exception {
189178 }
190179 }
191180
181+ @ Test
182+ public void allowUserToUpdateExpiredPasswordInteractivelyWithoutBeingPrompted () throws Exception {
183+ //given a user that require a password change
184+ int majorVersion = getVersionAndCreateUserWithPasswordChangeRequired ();
185+
186+ //when the user attempts a non-interactive password update
187+ assumeTrue (majorVersion >= 4 );
188+ baos .reset ();
189+ assertEquals ( EXIT_SUCCESS , main .runShell ( args ( SYSTEM_DB_NAME , "foo" , "pass" ,
190+ "ALTER CURRENT USER SET PASSWORD from \" pass\" to \" pass2\" ;" ), shell , mock ( Logger .class ) ) );
191+ //we shouldn't ask for a new password
192+ assertEquals ( "" , baos .toString () );
193+
194+ //then the new user should be able to successfully connect, and run a command
195+ assertEquals ( format ( "n%n42%n" ),
196+ executeNonInteractively ( args ( DEFAULT_DEFAULT_DB_NAME ,
197+ "foo" , "pass2" , "RETURN 42 AS n" ) ) );
198+ }
199+
200+ @ Test
201+ public void shouldFailIfNonInteractivelySettingPasswordOnNonSystemDb () throws Exception {
202+ //given a user that require a password change
203+ int majorVersion = getVersionAndCreateUserWithPasswordChangeRequired ();
204+
205+ //when
206+ assumeTrue ( majorVersion >= 4 );
207+
208+ //then
209+ assertEquals ( EXIT_FAILURE , main .runShell ( args ( DEFAULT_DEFAULT_DB_NAME , "foo" , "pass" ,
210+ "ALTER CURRENT USER SET PASSWORD from \" pass\" to \" pass2\" ;" ), shell , mock ( Logger .class ) ) );
211+ }
212+
213+ @ Test
214+ public void shouldBePromptedIfRunningNonInteractiveCypherThatDoesntUpdatePassword () throws Exception {
215+ //given a user that require a password change
216+ int majorVersion = getVersionAndCreateUserWithPasswordChangeRequired ();
217+
218+ //when
219+ assumeTrue ( majorVersion >= 4 );
220+
221+ //when interactively asked for a password use this
222+ inputBuffer .put ( String .format ( "pass2%n" ).getBytes () );
223+ baos .reset ();
224+ assertEquals ( EXIT_SUCCESS , main .runShell ( args ( DEFAULT_DEFAULT_DB_NAME , "foo" , "pass" ,
225+ "MATCH (n) RETURN n" ), shell , mock ( Logger .class ) ) );
226+
227+ //then should ask for a new password
228+ assertEquals ( format ( "Password change required%nnew password: *****%n" ), baos .toString () );
229+
230+ //then the new user should be able to successfully connect, and run a command
231+ assertEquals ( format ( "n%n42%n" ),
232+ executeNonInteractively ( args ( DEFAULT_DEFAULT_DB_NAME ,
233+ "foo" , "pass2" , "RETURN 42 AS n" ) ) );
234+ }
235+
192236 @ Test
193237 public void doesNotPromptToStdOutOnWrongAuthenticationIfOutputRedirected () throws Exception {
194238 // when
@@ -296,10 +340,15 @@ public void shouldReadMultipleCypherStatementsFromFile() throws Exception {
296340
297341 @ Test
298342 public void shouldFailIfInputFileDoesntExist () throws Exception {
299- // expect
300- exception .expect ( FileNotFoundException .class );
301- exception .expectMessage ( "what.cypher (No such file or directory)" );
302- executeFileNonInteractively ("what.cypher" );
343+ //given
344+ ByteArrayOutputStream out = new ByteArrayOutputStream ();
345+ Logger logger = new AnsiLogger ( false , Format .VERBOSE , new PrintStream ( out ), new PrintStream ( out ));
346+
347+ //when
348+ executeFileNonInteractively ("what.cypher" , logger );
349+
350+ //then
351+ assertEquals ( format ("what.cypher (No such file or directory)%n" ), out .toString ());
303352 }
304353
305354 @ Test
@@ -411,7 +460,7 @@ public void doesNotStartWhenDefaultDatabaseUnavailableIfInteractive() throws Exc
411460 assertEquals ("neo" , connectionConfig .password ());
412461
413462 // Stop the default database
414- shell .execute (":use " + DatabaseManager . SYSTEM_DB_NAME );
463+ shell .execute (":use " + SYSTEM_DB_NAME );
415464 shell .execute ("STOP DATABASE " + DatabaseManager .DEFAULT_DEFAULT_DB_NAME );
416465
417466 try {
@@ -453,7 +502,7 @@ public void startsAgainstSystemDatabaseWhenDefaultDatabaseUnavailableIfInteracti
453502 assertEquals ("neo" , connectionConfig .password ());
454503
455504 // Stop the default database
456- shell .execute (":use " + DatabaseManager . SYSTEM_DB_NAME );
505+ shell .execute (":use " + SYSTEM_DB_NAME );
457506 shell .execute ("STOP DATABASE " + DatabaseManager .DEFAULT_DEFAULT_DB_NAME );
458507
459508 try {
@@ -502,7 +551,7 @@ public void switchingToUnavailableDatabaseIfInteractive() throws Exception {
502551 assertEquals ("neo" , connectionConfig .password ());
503552
504553 // Stop the default database
505- shell .execute (":use " + DatabaseManager . SYSTEM_DB_NAME );
554+ shell .execute (":use " + SYSTEM_DB_NAME );
506555 shell .execute ("STOP DATABASE " + DatabaseManager .DEFAULT_DEFAULT_DB_NAME );
507556
508557 try {
@@ -540,7 +589,7 @@ public void switchingToUnavailableDefaultDatabaseIfInteractive() throws Exceptio
540589 assertEquals ("neo" , connectionConfig .password ());
541590
542591 // Stop the default database
543- shell .execute (":use " + DatabaseManager . SYSTEM_DB_NAME );
592+ shell .execute (":use " + SYSTEM_DB_NAME );
544593 shell .execute ("STOP DATABASE " + DatabaseManager .DEFAULT_DEFAULT_DB_NAME );
545594
546595 try {
@@ -554,23 +603,29 @@ public void switchingToUnavailableDefaultDatabaseIfInteractive() throws Exceptio
554603 }
555604 }
556605
557- private String executeFileNonInteractively (String filename ) throws Exception {
606+ private String executeFileNonInteractively (String filename ) {
558607 return executeFileNonInteractively (filename , mock (Logger .class ));
559608 }
560609
561- private String executeFileNonInteractively (String filename , Logger logger ) throws Exception
562- {
610+ private String executeFileNonInteractively (String filename , Logger logger ) {
563611 CliArgs cliArgs = new CliArgs ();
612+ cliArgs .setUsername ( USER , "" );
613+ cliArgs .setPassword ( PASSWORD , "" );
564614 cliArgs .setInputFilename (filename );
565615
616+ return executeNonInteractively ( cliArgs , logger );
617+ }
618+
619+ private String executeNonInteractively (CliArgs cliArgs ) {
620+ return executeNonInteractively (cliArgs , mock (Logger .class ));
621+ }
622+
623+ private String executeNonInteractively (CliArgs cliArgs , Logger logger )
624+ {
566625 ToStringLinePrinter linePrinter = new ToStringLinePrinter ();
567626 ShellAndConnection sac = getShell ( cliArgs , linePrinter );
568627 CypherShell shell = sac .shell ;
569- ConnectionConfig connectionConfig = sac .connectionConfig ;
570- main .connectMaybeInteractively ( shell , connectionConfig , true , true );
571- ShellRunner shellRunner = ShellRunner .getShellRunner (cliArgs , shell , logger , connectionConfig );
572- shellRunner .runUntilEnd ();
573-
628+ main .runShell (cliArgs , shell , logger );
574629 return linePrinter .result ();
575630 }
576631
@@ -626,4 +681,26 @@ private void exit( CypherShell shell ) throws CommandException
626681 //do nothing
627682 }
628683 }
684+
685+ private CliArgs args (String db , String user , String pass , String cypher )
686+ {
687+ CliArgs cliArgs = new CliArgs ();
688+ cliArgs .setUsername ( user , "" );
689+ cliArgs .setPassword ( pass , "" );
690+ cliArgs .setDatabase ( db );
691+ cliArgs .setCypher ( cypher );
692+ return cliArgs ;
693+ }
694+
695+ private int getVersionAndCreateUserWithPasswordChangeRequired () throws Exception {
696+ shell .setCommandHelper ( new CommandHelper ( mock ( Logger .class ), Historian .empty , shell ) );
697+
698+ main .connectMaybeInteractively ( shell , connectionConfig , true , true );
699+ String expectedLoginOutput = format ( "username: neo4j%npassword: ***%n" );
700+ assertEquals ( expectedLoginOutput , baos .toString () );
701+ ensureUser ();
702+ int majorVersion = majorVersion ( shell .getServerVersion () );
703+ shell .disconnect ();
704+ return majorVersion ;
705+ }
629706}
0 commit comments