11import { AbstractStartedContainer , ExecResult , GenericContainer , StartedTestContainer , Wait } from "testcontainers" ;
2+ import createKeyfile from "./keyfile" ;
23
34const MONGODB_PORT = 27017 ;
45
56export class MongoDBContainer extends GenericContainer {
7+ private username : string | null = null ;
8+ private password : string | null = null ;
9+ private keyfilePath : string | null = null ;
10+
611 constructor ( image = "mongo:4.0.1" ) {
712 super ( image ) ;
813 this . withExposedPorts ( MONGODB_PORT )
@@ -11,16 +16,46 @@ export class MongoDBContainer extends GenericContainer {
1116 . withStartupTimeout ( 120_000 ) ;
1217 }
1318
19+ public withUsername ( username : string ) : this {
20+ this . username = username ;
21+ return this ;
22+ }
23+
24+ public withPassword ( rootPassword : string ) : this {
25+ this . password = rootPassword ;
26+ return this ;
27+ }
28+
1429 public override async start ( ) : Promise < StartedMongoDBContainer > {
15- return new StartedMongoDBContainer ( await super . start ( ) ) ;
30+ if ( this . username && this . password ) {
31+ this . keyfilePath = createKeyfile ( ) ;
32+ const containerKeyfilePath = "/etc/mongo-keyfile" ;
33+ this . withBindMounts ( [
34+ {
35+ source : this . keyfilePath ,
36+ target : containerKeyfilePath ,
37+ mode : "ro" ,
38+ } ,
39+ ] ) ;
40+ this . withCommand ( [ "--replSet" , "rs0" , "--keyFile" , containerKeyfilePath , "--bind_ip_all" ] ) ;
41+ this . withEnvironment ( { MONGO_INITDB_ROOT_USERNAME : this . username , MONGO_INITDB_ROOT_PASSWORD : this . password } ) ;
42+ }
43+
44+ return new StartedMongoDBContainer ( await super . start ( ) , this . username , this . password ) ;
1645 }
1746
1847 protected override async containerStarted ( startedTestContainer : StartedTestContainer ) : Promise < void > {
48+ if ( this . username && this . password ) {
49+ await this . waitForRootUser ( startedTestContainer ) ;
50+ }
1951 await this . initReplicaSet ( startedTestContainer ) ;
2052 }
2153
2254 private async initReplicaSet ( startedTestContainer : StartedTestContainer ) {
23- await this . executeMongoEvalCommand ( startedTestContainer , "rs.initiate();" ) ;
55+ await this . executeMongoEvalCommand (
56+ startedTestContainer ,
57+ "rs.initiate({ _id: 'rs0', members: [ { _id: 0, host: '127.0.0.1:27017' } ] });"
58+ ) ;
2459 await this . executeMongoEvalCommand ( startedTestContainer , this . buildMongoWaitCommand ( ) ) ;
2560 }
2661
@@ -30,7 +65,25 @@ export class MongoDBContainer extends GenericContainer {
3065 }
3166
3267 private buildMongoEvalCommand ( command : string ) {
33- return [ this . getMongoCmdBasedOnImageTag ( ) , "--eval" , command ] ;
68+ const cmd = [ this . getMongoCmdBasedOnImageTag ( ) ] ;
69+
70+ if ( this . username && this . password ) {
71+ cmd . push (
72+ "admin" ,
73+ "--port" ,
74+ MONGODB_PORT . toString ( ) ,
75+ "-u" ,
76+ this . username ,
77+ "-p" ,
78+ this . password ,
79+ "--authenticationDatabase" ,
80+ "admin"
81+ ) ;
82+ }
83+
84+ cmd . push ( "--eval" , command ) ;
85+
86+ return cmd ;
3487 }
3588
3689 private getMongoCmdBasedOnImageTag ( ) {
@@ -44,6 +97,20 @@ export class MongoDBContainer extends GenericContainer {
4497 }
4598 }
4699
100+ private async waitForRootUser ( startedTestContainer : StartedTestContainer ) {
101+ const checkCommand = this . buildMongoEvalCommand ( "db.runCommand({ connectionStatus: 1 })" ) ;
102+
103+ for ( let i = 0 ; i < 30 ; i ++ ) {
104+ const result = await startedTestContainer . exec ( checkCommand ) ;
105+ if ( result . exitCode === 0 && result . output . includes ( `${ this . username } ` ) ) {
106+ return ;
107+ }
108+ await new Promise ( ( res ) => setTimeout ( res , 1000 ) ) ;
109+ }
110+
111+ throw new Error ( "Root user not ready after 30s" ) ;
112+ }
113+
47114 private buildMongoWaitCommand ( ) {
48115 return `
49116 var attempt = 0;
@@ -58,11 +125,19 @@ export class MongoDBContainer extends GenericContainer {
58125}
59126
60127export class StartedMongoDBContainer extends AbstractStartedContainer {
61- constructor ( startedTestContainer : StartedTestContainer ) {
128+ constructor (
129+ startedTestContainer : StartedTestContainer ,
130+ private readonly username : string | null ,
131+ private readonly password : string | null
132+ ) {
62133 super ( startedTestContainer ) ;
63134 }
64135
65136 public getConnectionString ( ) : string {
137+ if ( this . username !== null && this . password !== null ) {
138+ return `mongodb://${ this . username } :${ this . password } @${ this . getHost ( ) } :${ this . getMappedPort ( MONGODB_PORT ) } ` ;
139+ }
140+
66141 return `mongodb://${ this . getHost ( ) } :${ this . getMappedPort ( MONGODB_PORT ) } ` ;
67142 }
68143}
0 commit comments