Skip to content

Commit

Permalink
Implement sagas for save and upsate of SUMA settings (#2338)
Browse files Browse the repository at this point in the history
* Add save and update software updates settings sagas

* Add a test for new errors action

* Fix things from review
  • Loading branch information
dottorblaster authored Feb 20, 2024
1 parent c7bf223 commit cb00d11
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 11 deletions.
49 changes: 48 additions & 1 deletion assets/js/state/sagas/softwareUpdatesSettings.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { get } from 'lodash';
import { put, call, takeEvery } from 'redux-saga/effects';
import { getSettings } from '@lib/api/softwareUpdatesSettings';
import {
getSettings,
saveSettings,
updateSettings,
} from '@lib/api/softwareUpdatesSettings';

import {
FETCH_SOFTWARE_UPDATES_SETTINGS,
SAVE_SOFTWARE_UPDATES_SETTINGS,
UPDATE_SOFTWARE_UPDATES_SETTINGS,
startLoadingSoftwareUpdatesSettings,
setSoftwareUpdatesSettings,
setEmptySoftwareUpdatesSettings,
setSoftwareUpdatesSettingsErrors,
} from '@state/softwareUpdatesSettings';

export function* fetchSoftwareUpdatesSettings() {
Expand All @@ -19,9 +27,48 @@ export function* fetchSoftwareUpdatesSettings() {
}
}

export function* saveSoftwareUpdatesSettings({
url,
username,
password,
ca_cert,
}) {
yield put(startLoadingSoftwareUpdatesSettings());

try {
const response = yield call(saveSettings, {
url,
username,
password,
ca_cert,
});
yield put(setSoftwareUpdatesSettings(response.data));
} catch (error) {
const errors = get(error, ['response', 'data', 'errors'], []);
yield put(setSoftwareUpdatesSettingsErrors(errors));
}
}

export function* updateSoftwareUpdatesSettings(payload) {
yield put(startLoadingSoftwareUpdatesSettings());

try {
const response = yield call(updateSettings, payload);
yield put(setSoftwareUpdatesSettings(response.data));
} catch (error) {
const errors = get(error, ['response', 'data', 'errors'], []);
yield put(setSoftwareUpdatesSettingsErrors(errors));
}
}

export function* watchSoftwareUpdateSettings() {
yield takeEvery(
FETCH_SOFTWARE_UPDATES_SETTINGS,
fetchSoftwareUpdatesSettings
);
yield takeEvery(SAVE_SOFTWARE_UPDATES_SETTINGS, saveSoftwareUpdatesSettings);
yield takeEvery(
UPDATE_SOFTWARE_UPDATES_SETTINGS,
updateSoftwareUpdatesSettings
);
}
148 changes: 145 additions & 3 deletions assets/js/state/sagas/softwareUpdatesSettings.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { faker } from '@faker-js/faker';

import { recordSaga } from '@lib/test-utils';

import { networkClient } from '@lib/network';
Expand All @@ -7,16 +9,20 @@ import { softwareUpdatesSettingsFactory } from '@lib/test-utils/factories/softwa
import {
startLoadingSoftwareUpdatesSettings,
setSoftwareUpdatesSettings,
setSoftwareUpdatesSettingsErrors,
setEmptySoftwareUpdatesSettings,
} from '@state/softwareUpdatesSettings';

import { fetchSoftwareUpdatesSettings } from './softwareUpdatesSettings';

const axiosMock = new MockAdapter(networkClient);
import {
fetchSoftwareUpdatesSettings,
saveSoftwareUpdatesSettings,
updateSoftwareUpdatesSettings,
} from './softwareUpdatesSettings';

describe('Software Updates Settings saga', () => {
describe('Fetching Software Updates Settings', () => {
it('should successfully fetch software updates settings', async () => {
const axiosMock = new MockAdapter(networkClient);
const successfulResponse = softwareUpdatesSettingsFactory.build();

axiosMock
Expand All @@ -32,6 +38,7 @@ describe('Software Updates Settings saga', () => {
});

it('should empty software updates settings on failed fetching', async () => {
const axiosMock = new MockAdapter(networkClient);
[404, 500].forEach(async (errorStatus) => {
axiosMock.onGet('/settings/suma_credentials').reply(errorStatus);

Expand All @@ -44,4 +51,139 @@ describe('Software Updates Settings saga', () => {
});
});
});

describe('Saving Software Updates settings', () => {
it('should successfully save software updates settings', async () => {
const axiosMock = new MockAdapter(networkClient);
const payload = {
url: faker.internet.url(),
username: faker.word.noun(),
password: faker.word.noun(),
ca_cert: faker.lorem.text(),
};
const caUploadedAt = faker.date.recent().toString();
const successfulResponse = softwareUpdatesSettingsFactory.build({
url: payload.url,
username: payload.username,
ca_uploaded_at: caUploadedAt,
});

axiosMock
.onPost('/settings/suma_credentials')
.reply(201, successfulResponse);

const dispatched = await recordSaga(saveSoftwareUpdatesSettings, payload);

expect(dispatched).toEqual([
startLoadingSoftwareUpdatesSettings(),
setSoftwareUpdatesSettings(successfulResponse),
]);
});

it('should have errors on failed saving', async () => {
const axiosMock = new MockAdapter(networkClient);
const payload = {
url: '',
username: '',
password: faker.word.noun(),
ca_cert: '',
};
const errors = [
{
detail: "can't be blank",
source: { pointer: '/url' },
title: 'Invalid value',
},
{
detail: "can't be blank",
source: { pointer: '/ca_cert' },
title: 'Invalid value',
},
];

axiosMock.onPost('/settings/suma_credentials', payload).reply(422, {
errors,
});

const dispatched = await recordSaga(saveSoftwareUpdatesSettings, payload);

expect(dispatched).toEqual([
startLoadingSoftwareUpdatesSettings(),
setSoftwareUpdatesSettingsErrors(errors),
]);
});
});

describe('Updating Software Updates settings', () => {
it('should successfully change software updates settings', async () => {
const axiosMock = new MockAdapter(networkClient);
const payload = {
url: faker.internet.url(),
username: faker.word.noun(),
password: faker.word.noun(),
ca_cert: faker.lorem.text(),
};
const caUploadedAt = faker.date.recent().toString();
const successfulResponse = softwareUpdatesSettingsFactory.build({
url: payload.url,
username: payload.username,
ca_uploaded_at: caUploadedAt,
});

axiosMock
.onPatch('/settings/suma_credentials')
.reply(200, successfulResponse);

const dispatched = await recordSaga(
updateSoftwareUpdatesSettings,
payload
);

expect(dispatched).toEqual([
startLoadingSoftwareUpdatesSettings(),
setSoftwareUpdatesSettings(successfulResponse),
]);
});

it('should have errors on failed update', async () => {
const axiosMock = new MockAdapter(networkClient);
const payload = {
url: '',
username: '',
password: faker.word.noun(),
ca_cert: '',
};
const errors = [
{
detail: "can't be blank",
source: { pointer: '/url' },
title: 'Invalid value',
},
{
detail: "can't be blank",
source: { pointer: '/username' },
title: 'Invalid value',
},
{
detail: "can't be blank",
source: { pointer: '/ca_cert' },
title: 'Invalid value',
},
];

axiosMock.onPatch('/settings/suma_credentials', payload).reply(422, {
errors,
});

const dispatched = await recordSaga(
updateSoftwareUpdatesSettings,
payload
);

expect(dispatched).toEqual([
startLoadingSoftwareUpdatesSettings(),
setSoftwareUpdatesSettingsErrors(errors),
]);
});
});
});
23 changes: 20 additions & 3 deletions assets/js/state/softwareUpdatesSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const emptySettings = {
const initialState = {
loading: false,
settings: emptySettings,
error: null,
networkError: null,
errors: [],
};

export const softwareUpdatesSettingsSlice = createSlice({
Expand All @@ -24,27 +25,43 @@ export const softwareUpdatesSettingsSlice = createSlice({
},
setSoftwareUpdatesSettings: (state, { payload: settings }) => {
state.loading = false;
state.error = null;
state.networkError = null;
state.settings = settings;
},
setEmptySoftwareUpdatesSettings: (state) => {
state.loading = false;
state.error = null;
state.networkError = null;
state.settings = emptySettings;
},
setSoftwareUpdatesSettingsErrors: (state, { payload: errors }) => {
state.loading = false;
state.networkError = null;
state.errors = errors;
},
},
});

export const FETCH_SOFTWARE_UPDATES_SETTINGS =
'FETCH_SOFTWARE_UPDATES_SETTINGS';
export const SAVE_SOFTWARE_UPDATES_SETTINGS = 'SAVE_SOFTWARE_UPDATES_SETTINGS';
export const UPDATE_SOFTWARE_UPDATES_SETTINGS =
'UPDATE_SOFTWARE_UPDATES_SETTINGS';

export const fetchSoftwareUpdatesSettings = createAction(
FETCH_SOFTWARE_UPDATES_SETTINGS
);
export const saveSoftwareUpdatesSettings = createAction(
SAVE_SOFTWARE_UPDATES_SETTINGS
);
export const updateSoftwareUpdatesSettings = createAction(
UPDATE_SOFTWARE_UPDATES_SETTINGS
);

export const {
startLoadingSoftwareUpdatesSettings,
setSoftwareUpdatesSettings,
setEmptySoftwareUpdatesSettings,
setSoftwareUpdatesSettingsErrors,
} = softwareUpdatesSettingsSlice.actions;

export default softwareUpdatesSettingsSlice.reducer;
55 changes: 51 additions & 4 deletions assets/js/state/softwareUpdatesSettings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import softwareUpdatesSettingsReducer, {
startLoadingSoftwareUpdatesSettings,
setSoftwareUpdatesSettings,
setEmptySoftwareUpdatesSettings,
setSoftwareUpdatesSettingsErrors,
} from './softwareUpdatesSettings';

describe('SoftwareUpdateSettings reducer', () => {
Expand Down Expand Up @@ -29,7 +30,8 @@ describe('SoftwareUpdateSettings reducer', () => {
username: undefined,
ca_uploaded_at: undefined,
},
error: null,
networkError: null,
errors: [],
};

const settings = {
Expand All @@ -45,7 +47,8 @@ describe('SoftwareUpdateSettings reducer', () => {
expect(actual).toEqual({
loading: false,
settings,
error: null,
networkError: null,
errors: [],
});
});

Expand All @@ -57,7 +60,8 @@ describe('SoftwareUpdateSettings reducer', () => {
username: 'username',
ca_uploaded_at: '2021-01-01T00:00:00Z',
},
error: null,
networkError: null,
errors: [],
};

const action = setEmptySoftwareUpdatesSettings();
Expand All @@ -71,7 +75,50 @@ describe('SoftwareUpdateSettings reducer', () => {
username: undefined,
ca_uploaded_at: undefined,
},
error: null,
networkError: null,
errors: [],
});
});

it('should set errors upon validation failed', () => {
const initialState = {
loading: false,
settings: {
url: 'https://valid.url',
username: 'username',
ca_uploaded_at: '2021-01-01T00:00:00Z',
},
networkError: null,
errors: [],
};

const errors = [
{
detail: "can't be blank",
source: { pointer: '/url' },
title: 'Invalid value',
},
{
detail: "can't be blank",
source: { pointer: '/ca_cert' },
title: 'Invalid value',
},
];

const action = setSoftwareUpdatesSettingsErrors(errors);

const actual = softwareUpdatesSettingsReducer(initialState, action);

expect(actual).toEqual({
loading: false,

settings: {
url: 'https://valid.url',
username: 'username',
ca_uploaded_at: '2021-01-01T00:00:00Z',
},
networkError: null,
errors,
});
});
});

0 comments on commit cb00d11

Please sign in to comment.