Skip to content

Commit

Permalink
Fix data resolver API (#1052)
Browse files Browse the repository at this point in the history
* fix: data resolver errors

Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 authored Feb 14, 2024
1 parent eba4a27 commit 9f0a1c6
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function stubCheServerOptionsRequests(server: FastifyInstance) {
// stub OPTIONS requests to '/api/' since they fail when running the backend locally.
server.addHook('onRequest', (request, reply, done) => {
if ((request.url === '/api' || request.url === '/api/') && request.method === 'OPTIONS') {
return reply.send({
reply.send({
implementationVersion: 'Local Run',
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard-backend/src/localRun/routes/dexCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export function registerDexCallback(server: FastifyInstance) {
try {
const { token } = await server.localStart.getAccessTokenFromAuthorizationCodeFlow(request);
process.env.CLUSTER_ACCESS_TOKEN = token.access_token;
return reply.redirect('/dashboard/');
reply.redirect('/dashboard/');
} catch (e) {
// handle an error that usually occurs during the authorization flow from an abandoned tab with outdated state
return reply.redirect('/oauth/sign_in');
reply.redirect('/oauth/sign_in');
}
});
}
2 changes: 1 addition & 1 deletion packages/dashboard-backend/src/localRun/routes/signOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
export function registerSignOut(server: FastifyInstance) {
server.get('/oauth/sign_out', async function (request: FastifyRequest, reply: FastifyReply) {
process.env.CLUSTER_ACCESS_TOKEN = '';
return reply.redirect('/oauth/sign_in');
reply.redirect('/oauth/sign_in');
});
}
130 changes: 102 additions & 28 deletions packages/dashboard-backend/src/routes/api/__tests__/dataResolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
import { FastifyInstance } from 'fastify';

import { baseApiPath } from '@/constants/config';
import { axiosInstance } from '@/routes/api/helpers/getCertificateAuthority';
import { axiosInstance, axiosInstanceNoCert } from '@/routes/api/helpers/getCertificateAuthority';
import { createFastifyError } from '@/services/helpers';
import { setup, teardown } from '@/utils/appBuilder';

jest.mock('@/routes/api/helpers/getDevWorkspaceClient.ts');
jest.mock('@/routes/api/helpers/getToken.ts');

jest.mock('@/routes/api/helpers/getCertificateAuthority');
const getAxiosInstanceMock = jest.fn();
(axiosInstance.get as jest.Mock).mockImplementation(getAxiosInstanceMock);
const axiosInstanceMock = jest.fn();
(axiosInstance.get as jest.Mock).mockImplementation(axiosInstanceMock);
const defaultAxiosInstanceMock = jest.fn();
(axiosInstanceNoCert.get as jest.Mock).mockImplementation(defaultAxiosInstanceMock);

describe('Data Resolver Route', () => {
let app: FastifyInstance;
Expand All @@ -34,37 +34,111 @@ describe('Data Resolver Route', () => {
teardown(app);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('POST ${baseApiPath}/data/resolver', () => {
test('file exists', async () => {
const devfileContent = 'devfile content';
getAxiosInstanceMock.mockResolvedValue({
status: 200,
data: devfileContent,
describe('with certificate authority', () => {
beforeEach(() => {
defaultAxiosInstanceMock.mockRejectedValueOnce({
response: {
headers: {},
status: 500,
config: {},
statusText: '500 Internal Server Error',
data: createFastifyError(
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
'Internal Server Error',
500,
),
},
});
});

test('file exists', async () => {
axiosInstanceMock.mockResolvedValueOnce({
status: 200,
data: 'test content',
});

const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });

expect(defaultAxiosInstanceMock).toHaveBeenCalledTimes(1);
expect(axiosInstanceMock).toHaveBeenCalledTimes(1);
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual('test content');
});

const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });
test('file not found', async () => {
axiosInstanceMock.mockRejectedValueOnce({
response: {
headers: {},
status: 404,
config: {},
statusText: '404 Not Found',
data: createFastifyError('ERR_BAD_REQUEST', 'Not Found', 404),
},
});

const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });

expect(res.statusCode).toEqual(200);
expect(res.body).toEqual(devfileContent);
expect(defaultAxiosInstanceMock).toHaveBeenCalledTimes(1);
expect(axiosInstanceMock).toHaveBeenCalledTimes(1);
expect(res.statusCode).toEqual(404);
expect(res.body).toEqual(
'{"statusCode":404,"code":"ERR_BAD_REQUEST","error":"Not Found","message":"Not Found"}',
);
});
});
describe('without certificate authority', () => {
test('file exists', async () => {
defaultAxiosInstanceMock.mockResolvedValueOnce({
status: 200,
data: 'test content',
});

test('file not found', async () => {
const responseText = 'not found';
getAxiosInstanceMock.mockResolvedValue({
status: 404,
data: responseText,
const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });

expect(defaultAxiosInstanceMock).toHaveBeenCalledTimes(1);
expect(axiosInstanceMock).toHaveBeenCalledTimes(0);
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual('test content');
});

const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });
test('file not found', async () => {
const fastifyError = createFastifyError('ERR_BAD_REQUEST', 'Not Found', 404);
defaultAxiosInstanceMock.mockRejectedValueOnce({
response: {
headers: {},
status: 404,
config: {},
statusText: '404 Not Found',
data: fastifyError,
},
});

const res = await app
.inject()
.post(`${baseApiPath}/data/resolver`)
.payload({ url: 'https://devfile.yaml' });

expect(res.statusCode).toEqual(404);
expect(res.body).toEqual(responseText);
expect(defaultAxiosInstanceMock).toHaveBeenCalledTimes(1);
expect(axiosInstanceMock).toHaveBeenCalledTimes(0);
expect(res.statusCode).toEqual(404);
expect(res.body).toEqual(
'{"statusCode":404,"code":"ERR_BAD_REQUEST","error":"Not Found","message":"Not Found"}',
);
});
});
});
});
27 changes: 18 additions & 9 deletions packages/dashboard-backend/src/routes/api/dataResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
* Red Hat, Inc. - initial API and implementation
*/

import axios, { AxiosResponse } from 'axios';
import { helpers } from '@eclipse-che/common';
import { AxiosResponse } from 'axios';
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';

import { baseApiPath } from '@/constants/config';
import { dataResolverSchema } from '@/constants/schemas';
import { restParams } from '@/models';
import { axiosInstance } from '@/routes/api/helpers/getCertificateAuthority';
import { axiosInstance, axiosInstanceNoCert } from '@/routes/api/helpers/getCertificateAuthority';
import { getSchema } from '@/services/helpers';

const tags = ['Data Resolver'];
Expand All @@ -33,15 +34,23 @@ export function registerDataResolverRoute(instance: FastifyInstance) {
async function (request: FastifyRequest, reply: FastifyReply): Promise<string | void> {
const { url } = request.body as restParams.IYamlResolverParams;

let response: AxiosResponse;
try {
response = await axios.get(url, config);
} catch (e) {
response = await axiosInstance.get(url, config);
let response: AxiosResponse;
try {
response = await axiosInstanceNoCert.get(url, config);
} catch (error) {
if (helpers.errors.includesAxiosResponse(error) && error.response.status === 404) {
throw error;
}
response = await axiosInstance.get(url, config);
}
return response.data;
} catch (error) {
if (!helpers.errors.includesAxiosResponse(error)) {
throw error;
}
reply.code(error.response.status).send(error.response.data);
}
reply.code(response.status);
reply.send(response.data);
return reply;
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ export function registerDevWorkspaceTemplates(instance: FastifyInstance) {
request.params as restParams.INamespacedTemplateParams;
const token = getToken(request);
const { devWorkspaceTemplateApi: templateApi } = getDevWorkspaceClient(token);

await templateApi.delete(namespace, templateName);
reply.code(204);
return reply.send();

reply.code(204).send();
},
);
}
Expand Down
11 changes: 5 additions & 6 deletions packages/dashboard-backend/src/routes/api/devworkspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ export function registerDevworkspacesRoutes(instance: FastifyInstance) {
const { devworkspaceApi } = getDevWorkspaceClient(token);
const { headers, devWorkspace } = await devworkspaceApi.create(devworkspace, namespace);

reply.headers(headers);
reply.send(devWorkspace);
reply.headers(headers).send(devWorkspace);
},
);

Expand Down Expand Up @@ -89,8 +88,7 @@ export function registerDevworkspacesRoutes(instance: FastifyInstance) {
patch,
);

reply.headers(headers);
reply.send(devWorkspace);
reply.headers(headers).send(devWorkspace);
},
);

Expand All @@ -111,9 +109,10 @@ export function registerDevworkspacesRoutes(instance: FastifyInstance) {
request.params as restParams.INamespacedWorkspaceParams;
const token = getToken(request);
const { devworkspaceApi } = getDevWorkspaceClient(token);

await devworkspaceApi.delete(namespace, workspaceName);
reply.code(204);
return reply.send();

reply.code(204).send();
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ const certificateAuthority = getCertificateAuthority(
CHE_SELF_SIGNED_MOUNT_PATH ? CHE_SELF_SIGNED_MOUNT_PATH : DEFAULT_CHE_SELF_SIGNED_MOUNT_PATH,
);

export const axiosInstance = axios.create({
httpsAgent: new https.Agent({
ca: certificateAuthority,
}),
});
export const axiosInstanceNoCert = axios.create();
export const axiosInstance =
certificateAuthority !== undefined
? axios.create({
httpsAgent: new https.Agent({
ca: certificateAuthority,
}),
})
: axios.create();

function searchCertificate(
certPath: string,
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard-backend/src/routes/api/kubeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export function registerKubeConfigRoute(instance: FastifyInstance) {
const { kubeConfigApi } = getDevWorkspaceClient(token);
const { namespace, devworkspaceId } = request.params as restParams.INamespacedPodParams;
await kubeConfigApi.injectKubeConfig(namespace, devworkspaceId);
reply.code(204);
return reply.send();

reply.code(204).send();
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export function registerPersonalAccessTokenRoutes(instance: FastifyInstance) {
const { personalAccessTokenApi } = getDevWorkspaceClient(token);

await personalAccessTokenApi.delete(namespace, tokenName);
return reply.code(204).send();

reply.code(204).send();
},
);
});
Expand Down
3 changes: 1 addition & 2 deletions packages/dashboard-backend/src/routes/api/podmanLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export function registerPodmanLoginRoute(instance: FastifyInstance) {
const { podmanApi } = getDevWorkspaceClient(token);
const { namespace, devworkspaceId } = request.params as restParams.INamespacedPodParams;
await podmanApi.podmanLogin(namespace, devworkspaceId);
reply.code(204);
return reply.send();
reply.code(204).send();
},
);
});
Expand Down
3 changes: 2 additions & 1 deletion packages/dashboard-backend/src/routes/api/sshKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function registerSShKeysRoutes(instance: FastifyInstance) {
const { sshKeysApi } = getDevWorkspaceClient(token);

await sshKeysApi.delete(namespace, name);
return reply.code(204).send();

reply.code(204).send();
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ export function registerWorkspacePreferencesRoute(instance: FastifyInstance) {
const { namespace, provider } = request.params as restParams.IWorkspacePreferencesParams;
const token = getToken(request);
const { devWorkspacePreferencesApi } = getDevWorkspaceClient(token);

await devWorkspacePreferencesApi.removeProviderFromSkipAuthorizationList(
namespace,
provider,
);
reply.code(204);
return reply.send();

reply.code(204).send();
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function registerFactoryAcceptanceRedirect(instance: FastifyInstance): vo

const sanitizedQueryParams = helpers.sanitizeSearchParams(params);

return reply.redirect('/dashboard/#/load-factory?' + sanitizedQueryParams.toString());
reply.redirect('/dashboard/#/load-factory?' + sanitizedQueryParams.toString());
});
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard-backend/src/routes/workspaceRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function registerWorkspaceRedirect(instance: FastifyInstance): void {
const params = searchParams.get('params');
if (params) {
const parse: { namespace: string; workspace: string } = JSON.parse(params);
return reply.redirect(`/dashboard/#/ide/${parse.namespace}/${parse.workspace}`);
reply.redirect(`/dashboard/#/ide/${parse.namespace}/${parse.workspace}`);
}
});
});
Expand Down
Loading

0 comments on commit 9f0a1c6

Please sign in to comment.