// Copyright 2019-2020 The Kubeflow Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import express from 'express'; import fetch from 'node-fetch'; import requests from 'supertest'; import { UIServer } from './app'; import { loadConfigs } from './configs'; import { TEST_ONLY as K8S_TEST_EXPORT } from './k8s-helper'; import { Server } from 'http'; import { commonSetup } from './integration-tests/test-helper'; jest.mock('node-fetch'); // TODO: move sections of tests here to individual files in `frontend/server/integration-tests/` // for better organization and shorter/more focused tests. const mockedFetch: jest.Mock = fetch as any; describe('UIServer apis', () => { let app: UIServer; const tagName = '1.0.0'; const commitHash = 'abcdefg'; const { argv, buildDate, indexHtmlContent } = commonSetup({ tagName, commitHash }); afterEach(() => { if (app) { app.close(); } }); describe('/', () => { it('responds with unmodified index.html if it is not a kubeflow deployment', done => { const expectedIndexHtml = `
`; const configs = loadConfigs(argv, {}); app = new UIServer(configs); const request = requests(app.start()); request .get('/') .expect('Content-Type', 'text/html; charset=utf-8') .expect(200, expectedIndexHtml, done); }); it('responds with a modified index.html if it is a kubeflow deployment and sets HIDE_SIDENAV', done => { const expectedIndexHtml = ` `; const configs = loadConfigs(argv, { DEPLOYMENT: 'kubeflow' }); app = new UIServer(configs); const request = requests(app.start()); request .get('/') .expect('Content-Type', 'text/html; charset=utf-8') .expect(200, expectedIndexHtml, done); }); it('responds with flag DEPLOYMENT=MARKETPLACE if it is a marketplace deployment', done => { const expectedIndexHtml = ` `; const configs = loadConfigs(argv, { DEPLOYMENT: 'marketplace' }); app = new UIServer(configs); const request = requests(app.start()); request .get('/') .expect('Content-Type', 'text/html; charset=utf-8') .expect(200, expectedIndexHtml, done); }); }); it('responds with flag HIDE_SIDENAV=false even when DEPLOYMENT=KUBEFLOW', done => { const expectedIndexHtml = ` `; const configs = loadConfigs(argv, { DEPLOYMENT: 'KUBEFLOW', HIDE_SIDENAV: 'false' }); app = new UIServer(configs); const request = requests(app.start()); request .get('/') .expect('Content-Type', 'text/html; charset=utf-8') .expect(200, expectedIndexHtml, done); }); describe('/apis/v1beta1/healthz', () => { it('responds with apiServerReady to be false if ml-pipeline api server is not ready.', done => { (fetch as any).mockImplementationOnce((_url: string, _opt: any) => ({ json: () => Promise.reject('Unknown error'), })); const configs = loadConfigs(argv, {}); app = new UIServer(configs); requests(app.start()) .get('/apis/v1beta1/healthz') .expect( 200, { apiServerReady: false, buildDate, frontendCommitHash: commitHash, frontendTagName: tagName, }, done, ); }); it('responds with both ui server and ml-pipeline api state if ml-pipeline api server is also ready.', done => { (fetch as any).mockImplementationOnce((_url: string, _opt: any) => ({ json: () => Promise.resolve({ commit_sha: 'commit_sha', tag_name: '1.0.0', multi_user: false, }), })); const configs = loadConfigs(argv, {}); app = new UIServer(configs); requests(app.start()) .get('/apis/v1beta1/healthz') .expect( 200, { apiServerCommitHash: 'commit_sha', apiServerTagName: '1.0.0', apiServerMultiUser: false, multi_user: false, apiServerReady: true, buildDate, frontendCommitHash: commitHash, frontendTagName: tagName, }, done, ); }); }); describe('/system', () => { describe('/cluster-name', () => { it('responds with cluster name data from gke metadata', done => { mockedFetch.mockImplementationOnce((url: string, _opts: any) => url === 'http://metadata/computeMetadata/v1/instance/attributes/cluster-name' ? Promise.resolve({ ok: true, text: () => Promise.resolve('test-cluster') }) : Promise.reject('Unexpected request'), ); app = new UIServer(loadConfigs(argv, { DISABLE_GKE_METADATA: 'false' })); const request = requests(app.start()); request .get('/system/cluster-name') .expect('Content-Type', 'text/html; charset=utf-8') .expect(200, 'test-cluster', done); }); it('responds with 500 status code if corresponding endpoint is not ok', done => { mockedFetch.mockImplementationOnce((url: string, _opts: any) => url === 'http://metadata/computeMetadata/v1/instance/attributes/cluster-name' ? Promise.resolve({ ok: false, text: () => Promise.resolve('404 not found') }) : Promise.reject('Unexpected request'), ); app = new UIServer(loadConfigs(argv, { DISABLE_GKE_METADATA: 'false' })); const request = requests(app.start()); request.get('/system/cluster-name').expect(500, 'Failed fetching GKE cluster name', done); }); it('responds with endpoint disabled if DISABLE_GKE_METADATA env is true', done => { const configs = loadConfigs(argv, { DISABLE_GKE_METADATA: 'true' }); app = new UIServer(configs); const request = requests(app.start()); request .get('/system/cluster-name') .expect(500, 'GKE metadata endpoints are disabled.', done); }); }); describe('/project-id', () => { it('responds with project id data from gke metadata', done => { mockedFetch.mockImplementationOnce((url: string, _opts: any) => url === 'http://metadata/computeMetadata/v1/project/project-id' ? Promise.resolve({ ok: true, text: () => Promise.resolve('test-project') }) : Promise.reject('Unexpected request'), ); app = new UIServer(loadConfigs(argv, { DISABLE_GKE_METADATA: 'false' })); const request = requests(app.start()); request.get('/system/project-id').expect(200, 'test-project', done); }); it('responds with 500 status code if metadata request is not ok', done => { mockedFetch.mockImplementationOnce((url: string, _opts: any) => url === 'http://metadata/computeMetadata/v1/project/project-id' ? Promise.resolve({ ok: false, text: () => Promise.resolve('404 not found') }) : Promise.reject('Unexpected request'), ); app = new UIServer(loadConfigs(argv, { DISABLE_GKE_METADATA: 'false' })); const request = requests(app.start()); request.get('/system/project-id').expect(500, 'Failed fetching GKE project id', done); }); it('responds with endpoint disabled if DISABLE_GKE_METADATA env is true', done => { app = new UIServer(loadConfigs(argv, { DISABLE_GKE_METADATA: 'true' })); const request = requests(app.start()); request.get('/system/project-id').expect(500, 'GKE metadata endpoints are disabled.', done); }); }); }); describe('/k8s/pod', () => { let request: requests.SuperTest