Skip to content

Commit

Permalink
Add unit tests for @jupyterhub/binderhub-client
Browse files Browse the repository at this point in the history
- Re-adds #1741, but
  with [Jest](https://jestjs.io/) based unit tests. Jest seems fairly
  popular and is also what JupyterLab & JupyterHub admin uses, so it
  feels appropriate here.
- Runs Jest unit tests for each change in anything under js/, where
  the packages live.
- Use typed URL objects to construct build URLs, rather than just
  doing string concatenation. I'll try to slowly move all URL
  manipulation to the URL object
- Use the same babel config for binderhub-client as we do for binder,
  via a simlink
  • Loading branch information
yuvipanda committed Oct 3, 2023
1 parent 9e3ebef commit 0421989
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 12 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/jest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Runs jest based unit tests for the binderhub-client JS package
name: eslint

on:
pull_request:
paths:
- "js/**"
push:
paths:
- "js/**"
branches-ignore:
- "dependabot/**"
- "pre-commit-ci-update-config"
- "update-*"
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- run: |
cd js/packages/binderhub-client
npm install
- run: |
npm run jest
3 changes: 2 additions & 1 deletion binderhub/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ function build(providerSpec, log, fitAddon, path, pathType) {
$('.on-build').removeClass('hidden');

const buildToken = $("#build-token").data('token');
const image = new BinderRepository(providerSpec, BASE_URL, buildToken);
const buildEndpointUrl = new URL("build", new URL(BASE_URL, window.location.origin));
const image = new BinderRepository(providerSpec, buildEndpointUrl, buildToken);

image.onStateChange('*', function(oldState, newState, data) {
if (data.message !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions js/packages/binderhub-client/babel.config.json
32 changes: 21 additions & 11 deletions js/packages/binderhub-client/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@ export class BinderRepository {
/**
*
* @param {string} providerSpec Spec of the form <provider>/<repo>/<ref> to pass to the binderhub API.
* @param {string} baseUrl Base URL (including the trailing slash) of the binderhub installation to talk to.
* @param {string} buildToken Optional JWT based build token if this binderhub installation requires using build tokesn
* @param {URL} buildEndpointUrl API URL of the build endpoint to talk to
* @param {string} buildToken Optional JWT based build token if this binderhub installation requires using build tokens
*/
constructor(providerSpec, baseUrl, buildToken) {
constructor(providerSpec, buildEndpointUrl, buildToken) {
this.providerSpec = providerSpec;
this.baseUrl = baseUrl;
this.buildToken = buildToken;
// Make sure that buildEndpointUrl is a real URL - this ensures hostname is properly set
if(!(buildEndpointUrl instanceof URL)) {
throw new TypeError(`buildEndpointUrl must be a URL object, got ${buildEndpointUrl} instead`);
}
// We make a copy here so we don't modify the passed in URL object
this.buildEndpointUrl = new URL(buildEndpointUrl);
// The binderHub API is path based, so the buildEndpointUrl must have a trailing slash. We add
// it if it is not passed in here to us.
if(!this.buildEndpointUrl.pathname.endsWith('/')) {
this.buildEndpointUrl.pathname += "/";
}

// The actual URL we'll make a request to build this particular providerSpec
this.buildUrl = new URL(this.providerSpec, this.buildEndpointUrl);
if(buildToken) {
this.buildUrl.searchParams.append("build_token", buildToken);
}
this.callbacks = {};
this.state = null;
}
Expand All @@ -25,12 +40,7 @@ export class BinderRepository {
* Call the BinderHub API
*/
fetch() {
let apiUrl = this.baseUrl + "build/" + this.providerSpec;
if (this.buildToken) {
apiUrl = apiUrl + `?build_token=${this.buildToken}`;
}

this.eventSource = new EventSource(apiUrl);
this.eventSource = new EventSource(this.buildUrl);
this.eventSource.onerror = (err) => {
console.error("Failed to construct event stream", err);
this._changeState("failed", {
Expand Down
31 changes: 31 additions & 0 deletions js/packages/binderhub-client/lib/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BinderRepository } from ".";

test('Passed in URL object is not modified', () => {
const buildEndpointUrl = new URL("https://test-binder.org/build")
const br = new BinderRepository('gh/test/test', buildEndpointUrl, "token");
expect(br.buildEndpointUrl.toString()).not.toEqual(buildEndpointUrl.toString())
});

test('Invalid URL errors out', () => {
expect(() => {
new BinderRepository('gh/test/test', '/build', "token");
}).toThrow(TypeError);
});

test('Trailing slash added if needed', () => {
const buildEndpointUrl = new URL("https://test-binder.org/build")
const br = new BinderRepository('gh/test/test', buildEndpointUrl);
expect(br.buildEndpointUrl.toString()).toEqual("https://test-binder.org/build/")
});

test('Build URL correctly built from Build Endpoint', () => {
const buildEndpointUrl = new URL("https://test-binder.org/build")
const br = new BinderRepository('gh/test/test', buildEndpointUrl);
expect(br.buildUrl.toString()).toEqual("https://test-binder.org/build/gh/test/test");
});

test('Build URL correctly built from Build Endpoint when used with token', () => {
const buildEndpointUrl = new URL("https://test-binder.org/build")
const br = new BinderRepository('gh/test/test', buildEndpointUrl, 'token');
expect(br.buildUrl.toString()).toEqual("https://test-binder.org/build/gh/test/test?build_token=token");
});
11 changes: 11 additions & 0 deletions js/packages/binderhub-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,16 @@
"homepage": "https://github.com/jupyterhub/binderhub#readme",
"dependencies": {
"event-source-polyfill": "^1.0.31"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"babel-jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0"
},
"scripts": {
"jest": "jest"
},
"jest": {
"testEnvironment": "jsdom"
}
}

0 comments on commit 0421989

Please sign in to comment.