Skip to content

Commit ef3ff84

Browse files
committed
Corrects error message
1 parent 81fe9a5 commit ef3ff84

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

src/lib/ai/models/app_addons.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export default async function (config: Config, app: string) {
88
const {body: response} = await herokuClient.get<Heroku.AddOn>(`/apps/${app}/addons`, {
99
headers: {'Accept-Expansion': 'plan'},
1010
}).catch(error => {
11-
const error_ = error.body && error.body.message ? new Error(`The add-on was unable to be destroyed: ${error.body.message}.`) : new Error(`The add-on was unable to be destroyed: ${error}.`)
11+
const error_ = error.body && error.body.message ?
12+
new Error(`Unable to retrieve add-ons: ${error.body.message}`) :
13+
new Error(`Unable to retrieve add-ons: ${error}`)
1214
throw error_
1315
})
1416

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import {expect} from 'chai'
2+
import nock from 'nock'
3+
import appAddons from '../../../../src/lib/ai/models/app_addons'
4+
import {Config} from '@oclif/core'
5+
import {addon1, addon2} from '../../../helpers/fixtures'
6+
7+
describe('app_addons', function () {
8+
const {env} = process
9+
let api: nock.Scope
10+
let config: Config
11+
12+
beforeEach(function () {
13+
process.env = {}
14+
api = nock('https://api.heroku.com:443')
15+
config = {} as Config // Mock config
16+
})
17+
18+
afterEach(function () {
19+
process.env = env
20+
nock.cleanAll()
21+
})
22+
23+
context('when the API call succeeds', function () {
24+
it('returns the addons response', async function () {
25+
const mockAddons = [addon1, addon2]
26+
27+
api
28+
.get('/apps/test-app/addons')
29+
.query({})
30+
.reply(200, mockAddons)
31+
32+
const result = await appAddons(config, 'test-app')
33+
expect(result).to.deep.equal(mockAddons)
34+
})
35+
36+
it('returns empty array when no addons exist', async function () {
37+
api
38+
.get('/apps/empty-app/addons')
39+
.query({})
40+
.reply(200, [])
41+
42+
const result = await appAddons(config, 'empty-app')
43+
expect(result).to.deep.equal([])
44+
})
45+
})
46+
47+
context('when the app is not found', function () {
48+
it('throws an error with the correct message', async function () {
49+
api
50+
.get('/apps/nonexistent-app/addons')
51+
.query({})
52+
.reply(404, {
53+
id: 'not_found',
54+
message: 'Couldn\'t find that app.',
55+
})
56+
57+
try {
58+
await appAddons(config, 'nonexistent-app')
59+
expect.fail('Should have thrown an error')
60+
} catch (error: any) {
61+
expect(error.message).to.equal('Unable to retrieve add-ons: Couldn\'t find that app.')
62+
}
63+
})
64+
65+
it('throws an error with the correct message for different 404 responses', async function () {
66+
api
67+
.get('/apps/another-fake-app/addons')
68+
.query({})
69+
.reply(404, {
70+
id: 'not_found',
71+
message: 'App not found in this region.',
72+
})
73+
74+
try {
75+
await appAddons(config, 'another-fake-app')
76+
expect.fail('Should have thrown an error')
77+
} catch (error: any) {
78+
expect(error.message).to.equal('Unable to retrieve add-ons: App not found in this region.')
79+
}
80+
})
81+
})
82+
83+
context('when the API returns an error without a message', function () {
84+
it('throws an error with a fallback message', async function () {
85+
api
86+
.get('/apps/test-app/addons')
87+
.query({})
88+
.reply(500, {})
89+
90+
try {
91+
await appAddons(config, 'test-app')
92+
expect.fail('Should have thrown an error')
93+
} catch (error: any) {
94+
expect(error.message).to.include('Unable to retrieve add-ons:')
95+
expect(error.message).to.include('500')
96+
}
97+
})
98+
99+
it('throws an error with a fallback message for network errors', async function () {
100+
api
101+
.get('/apps/test-app/addons')
102+
.query({})
103+
.replyWithError('Network timeout')
104+
105+
try {
106+
await appAddons(config, 'test-app')
107+
expect.fail('Should have thrown an error')
108+
} catch (error: any) {
109+
expect(error.message).to.include('Unable to retrieve add-ons:')
110+
expect(error.message).to.include('Network timeout')
111+
}
112+
})
113+
})
114+
115+
context('when the API returns other error status codes', function () {
116+
it('throws an error with the correct message for 403 forbidden', async function () {
117+
api
118+
.get('/apps/restricted-app/addons')
119+
.query({})
120+
.reply(403, {
121+
id: 'forbidden',
122+
message: 'You do not have access to this app.',
123+
})
124+
125+
try {
126+
await appAddons(config, 'restricted-app')
127+
expect.fail('Should have thrown an error')
128+
} catch (error: any) {
129+
expect(error.message).to.equal('Unable to retrieve add-ons: You do not have access to this app.')
130+
}
131+
})
132+
133+
it('throws an error with the correct message for 422 validation error', async function () {
134+
api
135+
.get('/apps/invalid-app/addons')
136+
.query({})
137+
.reply(422, {
138+
id: 'validation_failed',
139+
message: 'Invalid app name format.',
140+
})
141+
142+
try {
143+
await appAddons(config, 'invalid-app')
144+
expect.fail('Should have thrown an error')
145+
} catch (error: any) {
146+
expect(error.message).to.equal('Unable to retrieve add-ons: Invalid app name format.')
147+
}
148+
})
149+
})
150+
151+
context('when the API returns malformed error responses', function () {
152+
it('handles error with empty body gracefully', async function () {
153+
api
154+
.get('/apps/test-app/addons')
155+
.query({})
156+
.reply(500, {})
157+
158+
try {
159+
await appAddons(config, 'test-app')
160+
expect.fail('Should have thrown an error')
161+
} catch (error: any) {
162+
expect(error.message).to.include('Unable to retrieve add-ons:')
163+
}
164+
})
165+
166+
it('handles error with undefined body gracefully', async function () {
167+
api
168+
.get('/apps/test-app/addons')
169+
.query({})
170+
.reply(500)
171+
172+
try {
173+
await appAddons(config, 'test-app')
174+
expect.fail('Should have thrown an error')
175+
} catch (error: any) {
176+
expect(error.message).to.include('Unable to retrieve add-ons:')
177+
}
178+
})
179+
})
180+
181+
context('API request details', function () {
182+
it('makes request with correct headers', async function () {
183+
const mockAddons = [addon1]
184+
185+
api
186+
.get('/apps/test-app/addons')
187+
.query({})
188+
.matchHeader('Accept-Expansion', 'plan')
189+
.reply(200, mockAddons)
190+
191+
await appAddons(config, 'test-app')
192+
// The test will fail if the header doesn't match
193+
})
194+
195+
it('uses the correct URL structure', async function () {
196+
const mockAddons = [addon1]
197+
198+
api
199+
.get('/apps/specific-app-name/addons')
200+
.query({})
201+
.reply(200, mockAddons)
202+
203+
const result = await appAddons(config, 'specific-app-name')
204+
expect(result).to.deep.equal(mockAddons)
205+
})
206+
})
207+
})

0 commit comments

Comments
 (0)