Skip to content

Commit e4f18e0

Browse files
committed
fix(web): accept legacy install-key success responses
- Preserve automatic key installation across supported Unraid releases. - Previously required a success status and message, rejecting valid legacy 2xx responses after the key could already be written. - Accept non-array response objects with a string status, an optional string message, and no error member. - Add historical response fixtures plus malformed and resolved-error regression coverage.
1 parent 9c13eb4 commit e4f18e0

2 files changed

Lines changed: 62 additions & 7 deletions

File tree

‎web/__test__/store/installKey.test.ts‎

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,33 @@ describe('InstallKey Store', () => {
113113
expect(store.keyType).toBe('license');
114114
});
115115

116+
it.each([
117+
{
118+
name: 'Unraid 6.12.15/7.0 stopped-array response',
119+
response: { status: 'Please Stop array to complete key installation' },
120+
},
121+
{
122+
name: 'Unraid 6.12.15/7.0 stopped response',
123+
response: { status: '' },
124+
},
125+
{
126+
name: 'Unraid 7.1.4 stopped response',
127+
response: { status: 'success' },
128+
},
129+
])('should accept the $name', async ({ response }) => {
130+
mockJsonFn.mockResolvedValueOnce(response);
131+
132+
await store.install(
133+
createTestAction({
134+
type: 'purchase',
135+
keyUrl: 'https://example.com/license.key',
136+
})
137+
);
138+
139+
expect(store.keyInstallStatus).toBe('success');
140+
expect(mockSetError).not.toHaveBeenCalled();
141+
});
142+
116143
it('should install trialStart keys using the same install flow', async () => {
117144
mockJsonFn.mockResolvedValueOnce({ status: 'success', message: 'Key installed' });
118145
const action = createTestAction({
@@ -221,8 +248,15 @@ describe('InstallKey Store', () => {
221248
});
222249
});
223250

224-
it('should reject an invalid success response', async () => {
225-
mockJsonFn.mockResolvedValueOnce({ success: true });
251+
it.each([
252+
{ name: 'response without a string status', response: { success: true } },
253+
{ name: 'array response', response: [{ status: 'success' }] },
254+
{
255+
name: 'response with a non-string optional message',
256+
response: { status: 'success', message: null },
257+
},
258+
])('should reject a $name', async ({ response }) => {
259+
mockJsonFn.mockResolvedValueOnce(response);
226260

227261
await store.install(
228262
createTestAction({
@@ -240,5 +274,25 @@ describe('InstallKey Store', () => {
240274
type: 'installKey',
241275
});
242276
});
277+
278+
it('should reject a resolved response containing an error', async () => {
279+
mockJsonFn.mockResolvedValueOnce({ status: 'success', error: 'download error 8' });
280+
281+
await store.install(
282+
createTestAction({
283+
type: 'purchase',
284+
keyUrl: 'https://example.com/license.key',
285+
})
286+
);
287+
288+
expect(store.keyInstallStatus).toBe('failed');
289+
expect(mockSetError).toHaveBeenCalledWith({
290+
heading: 'Failed to install key',
291+
message: 'download error 8',
292+
level: 'error',
293+
ref: 'installKey',
294+
type: 'installKey',
295+
});
296+
});
243297
});
244298
});

‎web/src/store/installKey.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { WebguiInstallKey } from '~/composables/services/webgui';
77
import { useErrorsStore } from '~/store/errors';
88

99
interface InstallKeySuccessResponse {
10-
message: string;
11-
status: 'success';
10+
message?: string;
11+
status: string;
1212
}
1313

1414
interface InstallKeyErrorResponse {
@@ -18,10 +18,11 @@ interface InstallKeyErrorResponse {
1818
const isInstallKeySuccessResponse = (response: unknown): response is InstallKeySuccessResponse =>
1919
typeof response === 'object' &&
2020
response !== null &&
21-
'message' in response &&
22-
typeof response.message === 'string' &&
21+
!Array.isArray(response) &&
2322
'status' in response &&
24-
response.status === 'success';
23+
typeof response.status === 'string' &&
24+
(!('message' in response) || typeof response.message === 'string') &&
25+
!('error' in response);
2526

2627
const isInstallKeyErrorResponse = (response: unknown): response is InstallKeyErrorResponse =>
2728
typeof response === 'object' &&

0 commit comments

Comments
 (0)