mirror of
https://github.com/NationalSecurityAgency/skills-client.git
synced 2026-11-13 18:52:42 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1925c31934 | ||
|
|
48ff8e052c | ||
|
|
037009f8a8 | ||
|
|
dca695d685 | ||
|
|
24905e9a42 | ||
|
|
5a98e7fd2c | ||
|
|
897d1b9b1c | ||
|
|
3a5bcc1f04 | ||
|
|
030a8bd24a | ||
|
|
532f33a8a7 | ||
|
|
b070e4058e |
No files matched your search
@@ -16,3 +16,5 @@
|
||||
/skills-client-ng/dist/
|
||||
/skills-client-ng/projects/skilltree/skills-client-ng/src/version.js
|
||||
/skills-client-integration/skills-int-e2e-test/cypress/downloads/*
|
||||
|
||||
AGENTS.md
|
||||
@@ -7,13 +7,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.1.1</version>
|
||||
<version>4.1.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>3.6.4-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-int-client-js",
|
||||
"version": "3.6.4",
|
||||
"version": "3.6.5",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib Integration Tests - Native JS",
|
||||
"main": "index.js",
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"author": "SkillTree Team",
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-js": "3.6.4",
|
||||
"@skilltree/skills-client-js": "3.6.5",
|
||||
"axios": "1.20.0",
|
||||
"bootstrap": "5.3.8",
|
||||
"jquery": "4.0.0"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.6.4-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-js</artifactId>
|
||||
|
||||
|
||||
@@ -45,6 +45,44 @@ const baseUrl = Cypress.config().baseUrl;
|
||||
const skillsDisplayHomePage = '/native/clientDisplay.html'
|
||||
|
||||
|
||||
// cy.request does not add Axios's X-XSRF-TOKEN header automatically. Keep the
|
||||
// cookie and header in sync for requests to the Skills Service, including
|
||||
// registration and login requests made before the browser visits the dashboard.
|
||||
Cypress.Commands.overwrite('request', (originalFn, ...args) => {
|
||||
const options = typeof args[0] === 'object'
|
||||
? { ...args[0] }
|
||||
: typeof args[1] === 'string'
|
||||
? { method: args[0], url: args[1], body: args[2] }
|
||||
: { url: args[0], body: args[1] };
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
const url = new URL(options.url, Cypress.config('baseUrl'));
|
||||
const backend = new URL(Cypress.config('baseUrl'));
|
||||
const isSkillsService = url.origin === backend.origin ||
|
||||
(url.hostname === backend.hostname && url.port === '8080');
|
||||
|
||||
if (!isSkillsService || ['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method)) {
|
||||
return originalFn(...args);
|
||||
}
|
||||
|
||||
return cy.getCookie('XSRF-TOKEN').then((cookie) => {
|
||||
if (cookie) {
|
||||
return originalFn({
|
||||
...options,
|
||||
headers: { ...options.headers, 'X-XSRF-TOKEN': cookie.value },
|
||||
});
|
||||
}
|
||||
|
||||
// The first request can be registration, logout, or login. A GET loads
|
||||
// the token without requiring an authenticated session.
|
||||
return cy.request({ url: `${url.origin}/app/userInfo` })
|
||||
.then(() => cy.getCookie('XSRF-TOKEN').should('exist'))
|
||||
.then((xsrfCookie) => originalFn({
|
||||
...options,
|
||||
headers: { ...options.headers, 'X-XSRF-TOKEN': xsrfCookie.value },
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('onlyOn', (enabled) => {
|
||||
if (enabled !== true) {
|
||||
cy.state('runnable').ctx.skip()
|
||||
@@ -59,15 +97,20 @@ Cypress.Commands.add("backendRegister", (user, pass, grantRoot) => {
|
||||
return cy.request(`${backend}/app/users/validExistingDashboardUserId/${user}`)
|
||||
.then((response) => {
|
||||
if (response.body !== true) {
|
||||
cy.log(`Creating user [${user}]`)
|
||||
cy.request('PUT', `${backend}/createAccount`, {
|
||||
|
||||
const userBody = {
|
||||
firstName: 'Firstname',
|
||||
lastName: 'LastName',
|
||||
email: user,
|
||||
password: pass,
|
||||
});
|
||||
}
|
||||
|
||||
if (grantRoot) {
|
||||
cy.request('POST', `${backend}/grantFirstRoot`);
|
||||
cy.log(`Creating root user [${user}]`)
|
||||
cy.request('PUT', `${backend}/createRootAccount`, userBody)
|
||||
} else {
|
||||
cy.log(`Creating user [${user}]`)
|
||||
cy.request('PUT', `${backend}/createAccount`, userBody);
|
||||
}
|
||||
cy.backendLogout()
|
||||
} else {
|
||||
@@ -230,18 +273,23 @@ Cypress.Commands.add('skillsLog', (message) => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('loginBySingleSignOn', (projId = 'proj1') => {
|
||||
Cypress.Commands.add('loginBySingleSignOn', () => {
|
||||
Cypress.log({
|
||||
name: 'loginBySingleSignOn',
|
||||
})
|
||||
|
||||
// first try to get a skills token,
|
||||
cy.request({
|
||||
url: `http://localhost:8080/api/projects/${projId}/token`,
|
||||
// Check the session without saving a protected request for Spring to replay
|
||||
// after OAuth login, before the test has created any projects.
|
||||
return cy.request({
|
||||
url: `${backend}/app/userInfo`,
|
||||
failOnStatusCode: false,
|
||||
}).then((tokenResp) => {
|
||||
if (tokenResp.status === 401) {
|
||||
cy.skillsLog('Skills token request failed, authenticating with OAuth provider...');
|
||||
followRedirect: false,
|
||||
}).then((sessionResp) => {
|
||||
const requiresLogin = sessionResp.status === 401 ||
|
||||
(sessionResp.status === 200 && !sessionResp.body?.userId) ||
|
||||
([301, 302, 303, 307, 308].includes(sessionResp.status) && Boolean(sessionResp.headers.location));
|
||||
if (requiresLogin) {
|
||||
cy.skillsLog('Skills session requires authentication, authenticating with OAuth provider...');
|
||||
cy.request({
|
||||
url: 'http://localhost:8080/oauth2/authorization/hydra',
|
||||
qs: { skillsRedirectUri: baseUrl, },
|
||||
@@ -253,10 +301,14 @@ Cypress.Commands.add('loginBySingleSignOn', (projId = 'proj1') => {
|
||||
const $html = Cypress.$(resp.body)
|
||||
const authenticityToken = $html.find('input[name=_csrf]').val()
|
||||
const challenge = $html.find('input[name=challenge]').val()
|
||||
expect(authenticityToken, 'OAuth login CSRF token').to.be.a('string').and.not.be.empty;
|
||||
expect(challenge, 'OAuth login challenge').to.be.a('string').and.not.be.empty;
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:3000/login',
|
||||
form: true, // we are submitting a regular form body
|
||||
followRedirect: false,
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
_csrf: authenticityToken,
|
||||
challenge,
|
||||
@@ -268,38 +320,53 @@ Cypress.Commands.add('loginBySingleSignOn', (projId = 'proj1') => {
|
||||
};
|
||||
|
||||
cy.request(options).then((resp2) => {
|
||||
expect(resp2.status).to.eq(200)
|
||||
expect(
|
||||
resp2.status,
|
||||
`OAuth login: ${JSON.stringify({
|
||||
location: resp2.headers.location,
|
||||
body: resp2.body,
|
||||
})}`,
|
||||
).to.eq(302);
|
||||
|
||||
if (resp2.redirects[resp2.redirects.length-1].includes('/consent?consent_challenge')) {
|
||||
cy.skillsLog('Granting consent with OAuth provider...');
|
||||
const $html = Cypress.$(resp2.body)
|
||||
const authenticityToken = $html.find('input[name=_csrf]').val()
|
||||
const challenge = $html.find('input[name=challenge]').val()
|
||||
// const consentUrl = resp2.redirects.filter(r => r.includes('/consent?consent_challenge'))[0].split(' ')[1]
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:3000/consent',
|
||||
form: true, // we are submitting a regular form body
|
||||
qs: { consent_challenge: challenge },
|
||||
body: {
|
||||
_csrf: authenticityToken,
|
||||
challenge,
|
||||
grant_scope: 'openid',
|
||||
// grant_scope: 'offline',
|
||||
submit: 'Allow access',
|
||||
remember: '1',
|
||||
},
|
||||
failOnStatusCode: false,
|
||||
};
|
||||
expect(resp2.headers.location, 'OAuth login redirect').to.be.a('string').and.not.be.empty;
|
||||
const redirectUrl = new URL(resp2.headers.location, options.url).href;
|
||||
return cy.request(redirectUrl).then((resp2) => {
|
||||
expect(resp2.status).to.eq(200)
|
||||
|
||||
cy.request(options).then((resp3) => {
|
||||
expect(resp3.status).to.eq(200)
|
||||
})
|
||||
}
|
||||
if ((resp2.redirects || []).some(redirect => redirect.includes('/consent?consent_challenge')) &&
|
||||
Cypress.$(resp2.body).find('input[name=challenge]').length > 0) {
|
||||
cy.skillsLog('Granting consent with OAuth provider...');
|
||||
const $html = Cypress.$(resp2.body)
|
||||
const authenticityToken = $html.find('input[name=_csrf]').val()
|
||||
const challenge = $html.find('input[name=challenge]').val()
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: 'http://localhost:3000/consent',
|
||||
form: true, // we are submitting a regular form body
|
||||
qs: { consent_challenge: challenge },
|
||||
body: {
|
||||
_csrf: authenticityToken,
|
||||
challenge,
|
||||
grant_scope: 'openid',
|
||||
submit: 'Allow access',
|
||||
remember: '1',
|
||||
},
|
||||
failOnStatusCode: false,
|
||||
};
|
||||
|
||||
cy.request(options).then((resp3) => {
|
||||
expect(resp3.status).to.eq(200)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
cy.skillsLog('Received Skills token, already authenticated with OAuth provider.');
|
||||
expect(sessionResp.status, 'Skills session response status').to.eq(200)
|
||||
cy.skillsLog('Skills session already authenticated with OAuth provider.');
|
||||
}
|
||||
})
|
||||
});
|
||||
}).then(() => cy.request(`${backend}/app/userInfo`).then((resp) => {
|
||||
expect(resp.status, 'Authenticated Skills session status').to.eq(200)
|
||||
expect(resp.body.userId, 'Authenticated Skills user').to.be.a('string').and.not.be.empty;
|
||||
}))
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.6.4-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-e2e-test</artifactId>
|
||||
<description>End to End tests for Skills client libs using skills-int-service app</description>
|
||||
|
||||
+2
@@ -37,3 +37,5 @@ logging.level.skills=DEBUG
|
||||
|
||||
skills.config.client.loggingEnabled=true
|
||||
skills.config.client.loggingLevel=TRACE
|
||||
|
||||
skills.config.client.loggingMaxRequestsPerMinute=5000
|
||||
+3
-1
@@ -26,4 +26,6 @@ skills.config.ui.userCommunityParagraphValidationMessage=May not contain divined
|
||||
skills.config.ui.userCommunityRestrictedDescriptor=Divine Dragon
|
||||
skills.config.ui.userCommunityBeforeLabel=For
|
||||
skills.config.ui.userCommunityAfterLabel=Nation
|
||||
skills.config.ui.defaultCommunityDescriptor=All Dragons
|
||||
skills.config.ui.defaultCommunityDescriptor=All Dragons
|
||||
|
||||
skills.config.client.loggingMaxRequestsPerMinute=5000
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.6.4-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-service</artifactId>
|
||||
<description>SkillTree Client Lib Integration Tests</description>
|
||||
|
||||
+3
@@ -128,6 +128,9 @@ public class Controller {
|
||||
|
||||
private void authIfNecessary() {
|
||||
if (!skillsConfig.getAuthMode().equalsIgnoreCase("pki")) {
|
||||
// Seed the session and XSRF-TOKEN before /performLogin, which requires CSRF validation.
|
||||
// SecurityRestTemplateCustomizer retains the cookies and sends X-XSRF-TOKEN on later requests.
|
||||
restTemplate.getForEntity(skillsConfig.getServiceUrl() + "/app/userInfo", String.class);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||
|
||||
+15
-2
@@ -16,6 +16,8 @@
|
||||
package skills.example;
|
||||
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.cookie.BasicCookieStore;
|
||||
import org.apache.hc.client5.http.cookie.Cookie;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
|
||||
@@ -45,12 +47,22 @@ public class SecurityRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
// not by default keeps track of session. This method is called by the
|
||||
// RestTemplateBuilder used in the skills.example.Controller constructor
|
||||
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
|
||||
HttpClient client = getHttpClient();
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
HttpClient client = getHttpClient(cookieStore);
|
||||
clientHttpRequestFactory.setHttpClient(client);
|
||||
restTemplate.setRequestFactory(clientHttpRequestFactory);
|
||||
restTemplate.getInterceptors().add((request, body, execution) -> {
|
||||
for (Cookie cookie : cookieStore.getCookies()) {
|
||||
if ("XSRF-TOKEN".equals(cookie.getName())) {
|
||||
request.getHeaders().set("X-XSRF-TOKEN", cookie.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return execution.execute(request, body);
|
||||
});
|
||||
}
|
||||
|
||||
private HttpClient getHttpClient() {
|
||||
private HttpClient getHttpClient(BasicCookieStore cookieStore) {
|
||||
try {
|
||||
SSLContext sslContext = SSLContexts.createSystemDefault();
|
||||
HostnameVerifier allowAllHosts = new NoopHostnameVerifier();
|
||||
@@ -67,6 +79,7 @@ public class SecurityRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
|
||||
return HttpClients.custom()
|
||||
.useSystemProperties()
|
||||
.setDefaultCookieStore(cookieStore)
|
||||
.setConnectionManager(poolingHttpClientConnectionManager)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-js",
|
||||
"version": "3.6.4",
|
||||
"version": "3.6.5",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Native JS Lib",
|
||||
"main": "dist/skills-client-js.umd.min.js",
|
||||
@@ -21,7 +21,7 @@
|
||||
"buildSkipTests": "npm run lint && npm run clean && npm run build:umd && npm run build:esm",
|
||||
"build:umd": "rollup --config --format umd --file dist/skills-client-js.umd.min.js --name SkillsClient --bundleConfigAsCjs",
|
||||
"build:esm": "rollup --config --format esm --file dist/skills-client-js.esm.min.js --bundleConfigAsCjs",
|
||||
"deploy-to-examples": "npm run build && cp ./dist/skills-display-js.umd.min.js ../skills-examples/skills-example-client-js/app/assets/js/@skilltree/skills-client-js/dist",
|
||||
"deploy-to-examples": "npm run clean && npm run build:umd && npm run build:esm && rm ../skills-client-integration/skills-int-service/src/main/resources/public/native/assets/js/@skilltree/skills-client-js/dist/* && cp ./dist/* ../skills-client-integration/skills-int-service/src/main/resources/public/native/assets/js/@skilltree/skills-client-js/dist/",
|
||||
"test": "NODE_ENV=test jest --coverage",
|
||||
"test-watch": "npx jest --passWithNoTests --coverage --watch",
|
||||
"prepublishOnly": "npm run build",
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', `${conf.getServiceUrl()}/api/projects/${conf.getProjectId()}/skillsClientVersion`);
|
||||
xhr.withCredentials = true;
|
||||
xhr.withCredentials = conf.isPKIMode();
|
||||
if (!conf.isPKIMode()) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${conf.getAuthToken()}`);
|
||||
}
|
||||
@@ -111,7 +111,6 @@ export default {
|
||||
xhr.open('GET', authenticator);
|
||||
} else {
|
||||
xhr.open('GET', `${serviceUrl}/api/projects/${projectId}/token`);
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class SkillsLevelJS {
|
||||
this._projectId = SkillsConfiguration.getProjectId();
|
||||
log.debug(`SkillsClient::SkillsLevelJS::getting projectId from SkillsConfiguration: [${this._projectId}]`);
|
||||
}
|
||||
const requestConfig = { withCredentials: true };
|
||||
const requestConfig = { withCredentials: SkillsConfiguration.isPKIMode() };
|
||||
if (!SkillsConfiguration.isPKIMode()) {
|
||||
const authToken = SkillsConfiguration.getAuthToken();
|
||||
requestConfig.headers = { Authorization: `Bearer ${authToken}` };
|
||||
|
||||
@@ -138,7 +138,7 @@ const reportInternal = (resolve, reject, userSkillId, timestamp, isRetry, retryA
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('POST', `${SkillsConfiguration.getServiceUrl()}/api/projects/${SkillsConfiguration.getProjectId()}/skills/${userSkillId}`);
|
||||
xhr.withCredentials = true;
|
||||
xhr.withCredentials = SkillsConfiguration.isPKIMode();
|
||||
if (!SkillsConfiguration.isPKIMode()) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${SkillsConfiguration.getAuthToken()}`);
|
||||
}
|
||||
|
||||
@@ -18,21 +18,33 @@ import mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
import { SkillsReporter } from '../../src/reporter/SkillsReporter.js';
|
||||
|
||||
// These tests exercise HTTP authentication, not the background websocket connection.
|
||||
jest.mock('@stomp/stompjs');
|
||||
|
||||
describe('authPkiTests()', () => {
|
||||
const mockServiceUrl = 'http://some.com';
|
||||
const mockProjectId = 'proj1';
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: 'pki',
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(async () => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
mock.get(/.*\/public\/status/, (req, res) => res.status(200).body('{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}'));
|
||||
mock.post(/.*\/api\/projects\/proj1\/skillsClientVersion/, (req, res) => res.status(200).body('{"success":true,"explanation":null}'));
|
||||
await SkillsConfiguration.configure({
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
authenticator: 'pki',
|
||||
});
|
||||
});
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => mock.setup());
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => mock.teardown());
|
||||
afterEach(() => {
|
||||
SkillsReporter.cancelRetryChecker();
|
||||
window.localStorage.removeItem('skillTreeRetryQueue');
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
});
|
||||
|
||||
it('successful call in pki mode', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
@@ -55,6 +55,8 @@ describe('userSkillsService', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
SkillsReporter.cancelRetryChecker();
|
||||
jest.restoreAllMocks();
|
||||
window.XMLHttpRequest = realXMLHttpRequest;
|
||||
});
|
||||
|
||||
@@ -79,9 +81,16 @@ describe('userSkillsService', () => {
|
||||
expect(MockXMLHttpRequest.setRequestHeader).toHaveBeenCalledWith('Authorization', `Bearer ${SkillsConfiguration.getAuthToken()}`);
|
||||
});
|
||||
|
||||
it('enables withCredentials', () => {
|
||||
it('disables withCredentials in token mode', () => {
|
||||
SkillsReporter.reportSkill(mockUserSkillId);
|
||||
expect(MockXMLHttpRequest.withCredentials).toBeTruthy();
|
||||
expect(MockXMLHttpRequest.withCredentials).toBe(false);
|
||||
});
|
||||
|
||||
it('enables withCredentials without an Authorization header in PKI mode', () => {
|
||||
SkillsConfiguration.setAuthToken('pki');
|
||||
SkillsReporter.reportSkill(mockUserSkillId);
|
||||
expect(MockXMLHttpRequest.withCredentials).toBe(true);
|
||||
expect(MockXMLHttpRequest.setRequestHeader).not.toHaveBeenCalledWith('Authorization', expect.anything());
|
||||
});
|
||||
|
||||
it('sends out the ajax request', () => {
|
||||
|
||||
Reference in new issue
Block a user