-
Notifications
You must be signed in to change notification settings - Fork 216
/
rotate.unit.test.js
247 lines (208 loc) · 7.89 KB
/
rotate.unit.test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
'use strict'
/* global beforeEach afterEach */
const cli = require('heroku-cli-util')
const {expect} = require('chai')
const nock = require('nock')
const proxyquire = require('proxyquire')
const db = {
database: 'mydb',
host: 'foo.com',
user: 'jeff',
password: 'pass',
url: {href: 'postgres://jeff:pass@foo.com/mydb'},
}
const addon = {
id: 1,
name: 'postgres-1',
plan: {name: 'heroku-postgresql:standard-0'},
}
const attachments = [
{
namespace: 'credential:my_role',
app: {name: 'appname_1'},
},
{
namespace: 'credential:my_role',
app: {name: 'appname_2'},
},
{
namespace: 'credential:other_role',
app: {name: 'appname_3'},
},
]
const fetcher = () => {
return {
database: () => db,
addon: () => addon,
}
}
const cmd = proxyquire('../../../../commands/credentials/rotate', {
'../../lib/fetcher': fetcher,
})
let lastApp
let lastConfirm
let lastMsg
const confirmApp = async function (app, confirm, msg) {
lastApp = app
lastConfirm = confirm
lastMsg = msg
}
describe('pg:credentials:rotate', () => {
let api
let pg
let starter
let confirm
beforeEach(() => {
api = nock('https://api.heroku.com')
api.get('/addons/postgres-1/addon-attachments').reply(200, attachments)
pg = nock('https://postgres-api.heroku.com')
starter = nock('https://postgres-starter-api.heroku.com')
confirm = cli.confirmApp
cli.confirmApp = confirmApp
cli.mockConsole()
cli.exit.mock()
})
afterEach(() => {
cli.confirmApp = confirm
nock.cleanAll()
api.done()
})
it('rotates credentials for a specific role with --name', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials/my_role/credentials_rotation').reply(200)
return cmd.run({app: 'myapp', args: {}, flags: {name: 'my_role', confirm: 'myapp'}})
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal('Rotating my_role on postgres-1... done\n'))
})
it('rotates credentials for all roles with --all', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials_rotation').reply(200)
return cmd.run({app: 'myapp', args: {}, flags: {all: true, confirm: 'myapp'}})
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal('Rotating all credentials on postgres-1... done\n'))
})
it('rotates credentials for a specific role with --name and --force', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials/my_role/credentials_rotation').reply(200)
return cmd.run({app: 'myapp', args: {}, flags: {name: 'my_role', confirm: 'myapp', force: true}})
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal('Rotating my_role on postgres-1... done\n'))
})
it('fails with an error if both --all and --name are included', () => {
const err = 'cannot pass both --all and --name'
return expect(cmd.run({app: 'myapp', args: {}, flags: {all: true, name: 'my_role', confirm: 'myapp'}})).to.be.rejectedWith(Error, err)
})
it('throws an error when the db is starter plan but the name is specified', () => {
const hobbyAddon = {
name: 'postgres-1',
plan: {name: 'heroku-postgresql:hobby-dev'},
}
const fetcher = () => {
return {
database: () => db,
addon: () => hobbyAddon,
}
}
const cmd = proxyquire('../../../../commands/credentials/rotate', {
'../../lib/fetcher': fetcher,
})
const err = 'Essential-tier databases support only one default credential.'
return expect(cmd.run({app: 'myapp', args: {}, flags: {name: 'jeff'}})).to.be.rejectedWith(Error, err)
})
it('rotates credentials with no --name with starter plan', () => {
const hobbyAddon = {
name: 'postgres-1',
plan: {name: 'heroku-postgresql:hobby-dev'},
}
const fetcher = () => {
return {
database: () => db,
addon: () => hobbyAddon,
}
}
const cmd = proxyquire('../../../../commands/credentials/rotate', {
'../../lib/fetcher': fetcher,
})
starter.post('/postgres/v0/databases/postgres-1/credentials/default/credentials_rotation').reply(200)
return cmd.run({app: 'myapp', args: {}, flags: {confirm: 'myapp'}})
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal('Rotating default on postgres-1... done\n'))
})
it('rotates credentials with --all with starter plan', () => {
const hobbyAddon = {
name: 'postgres-1',
plan: {name: 'heroku-postgresql:hobby-dev'},
}
const fetcher = () => {
return {
database: () => db,
addon: () => hobbyAddon,
}
}
const cmd = proxyquire('../../../../commands/credentials/rotate', {
'../../lib/fetcher': fetcher,
})
starter.post('/postgres/v0/databases/postgres-1/credentials_rotation').reply(200)
return cmd.run({app: 'myapp', args: {}, flags: {all: true, confirm: 'myapp'}})
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal('Rotating all credentials on postgres-1... done\n'))
})
it('requires app confirmation for rotating all roles with --all', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials_rotation').reply(200)
const message = `WARNING: Destructive Action
Connections will be reset and applications will be restarted.
This command will affect the apps appname_1, appname_2, appname_3.`
return cmd.run({app: 'myapp',
args: {},
flags: {all: true, confirm: 'myapp'}})
.then(() => {
expect(lastApp).to.equal('myapp')
expect(lastConfirm).to.equal('myapp')
expect(lastMsg).to.equal(message)
})
})
it('requires app confirmation for rotating all roles with --all and --force', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials_rotation').reply(200)
const message = `WARNING: Destructive Action
This forces rotation on all credentials including the default credential.
Connections will be reset and applications will be restarted.
Any followers lagging in replication (see heroku pg:info) will be inaccessible until caught up.
This command will affect the apps appname_1, appname_2, appname_3.`
return cmd.run({app: 'myapp',
args: {},
flags: {all: true, force: true, confirm: 'myapp'}})
.then(() => {
expect(lastApp).to.equal('myapp')
expect(lastConfirm).to.equal('myapp')
expect(lastMsg).to.equal(message)
})
})
it('requires app confirmation for rotating a specific role with --name', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials/my_role/credentials_rotation').reply(200)
const message = `WARNING: Destructive Action
The password for the my_role credential will rotate.
Connections older than 30 minutes will be reset, and a temporary rotation username will be used during the process.
This command will affect the apps appname_1, appname_2.`
return cmd.run({app: 'myapp',
args: {},
flags: {name: 'my_role', confirm: 'myapp'}})
.then(() => {
expect(lastApp).to.equal('myapp')
expect(lastConfirm).to.equal('myapp')
expect(lastMsg).to.equal(message)
})
})
it('requires app confirmation for force rotating a specific role with --name and --force', () => {
pg.post('/postgres/v0/databases/postgres-1/credentials/my_role/credentials_rotation').reply(200)
const message = `WARNING: Destructive Action
The password for the my_role credential will rotate.
Connections will be reset and applications will be restarted.
Any followers lagging in replication (see heroku pg:info) will be inaccessible until caught up.
This command will affect the apps appname_1, appname_2.`
return cmd.run({app: 'myapp',
args: {},
flags: {name: 'my_role', force: true, confirm: 'myapp'}})
.then(() => {
expect(lastApp).to.equal('myapp')
expect(lastConfirm).to.equal('myapp')
expect(lastMsg).to.equal(message)
})
})
})