mirror of
https://github.com/NationalSecurityAgency/skills-client.git
synced 2026-11-07 06:05:59 -10:00
Compare commits
184
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1925c31934 | ||
|
|
48ff8e052c | ||
|
|
037009f8a8 | ||
|
|
dca695d685 | ||
|
|
24905e9a42 | ||
|
|
5a98e7fd2c | ||
|
|
897d1b9b1c | ||
|
|
3a5bcc1f04 | ||
|
|
030a8bd24a | ||
|
|
532f33a8a7 | ||
|
|
b070e4058e | ||
|
|
6b1ba358d8 | ||
|
|
e845fb7788 | ||
|
|
599e838086 | ||
|
|
ad5ab89439 | ||
|
|
3c408f555f | ||
|
|
ea523e9381 | ||
|
|
cea08a3dd5 | ||
|
|
731853a8a6 | ||
|
|
78c338fd36 | ||
|
|
80052f77ac | ||
|
|
b4e7718741 | ||
|
|
42c40ce92f | ||
|
|
18ad1af38e | ||
|
|
58f6265836 | ||
|
|
e3f299376c | ||
|
|
2b66377a30 | ||
|
|
b88c19d0c7 | ||
|
|
558cecacdd | ||
|
|
dbcc95f951 | ||
|
|
50ac5c413f | ||
|
|
cf0961c435 | ||
|
|
56cb884fdb | ||
|
|
6af2944841 | ||
|
|
a301650700 | ||
|
|
d19947c814 | ||
|
|
361bae4289 | ||
|
|
aae9cc7ee9 | ||
|
|
60c3d5edb1 | ||
|
|
bb12395d80 | ||
|
|
dea819670b | ||
|
|
7553782dcc | ||
|
|
daf5b1fca8 | ||
|
|
83878d8c3a | ||
|
|
83178bf58b | ||
|
|
03cf175e26 | ||
|
|
04c9f2e950 | ||
|
|
3f952ba966 | ||
|
|
b7ad03fb64 | ||
|
|
39c7abc757 | ||
|
|
aa0af7e776 | ||
|
|
33cfff5ebb | ||
|
|
7efe886ef4 | ||
|
|
caa236dff1 | ||
|
|
2b5b28c8dc | ||
|
|
481a6d0505 | ||
|
|
5103989eef | ||
|
|
266bf9911d | ||
|
|
8ab6ab5a3a | ||
|
|
d747c2b96f | ||
|
|
002ab3f783 | ||
|
|
a5f3848c16 | ||
|
|
601c7a2a96 | ||
|
|
8fcc10d52f | ||
|
|
65881829c5 | ||
|
|
99f946f33a | ||
|
|
8051bf7a63 | ||
|
|
9863d82e97 | ||
|
|
a56fef71fb | ||
|
|
db24520d75 | ||
|
|
ca23b4e4d4 | ||
|
|
9e9952eb53 | ||
|
|
90d63e19dd | ||
|
|
fe91a737d5 | ||
|
|
419f5c9968 | ||
|
|
809e361760 | ||
|
|
f5cc10c7df | ||
|
|
c225388951 | ||
|
|
6d50e32d27 | ||
|
|
6711223ad1 | ||
|
|
4e04972a09 | ||
|
|
a22b53a356 | ||
|
|
5e5aefd745 | ||
|
|
54eca19513 | ||
|
|
41bfbcc9d9 | ||
|
|
be1e821973 | ||
|
|
e38c70d39a | ||
|
|
796f97342b | ||
|
|
02fe5e1c77 | ||
|
|
c9bfb0ca1f | ||
|
|
ad321f0169 | ||
|
|
be81478d02 | ||
|
|
03372a9e92 | ||
|
|
7ec6092d6f | ||
|
|
354500f018 | ||
|
|
7458284c30 | ||
|
|
f8156f45ab | ||
|
|
ce13000dcb | ||
|
|
1664fa7ac7 | ||
|
|
1806136418 | ||
|
|
281140c19f | ||
|
|
0763dfbaa3 | ||
|
|
b4a59fdb7e | ||
|
|
7759001feb | ||
|
|
1b3ac0ff11 | ||
|
|
d43f21a4cc | ||
|
|
2d946cb9d9 | ||
|
|
8da314393b | ||
|
|
e9a11fbbca | ||
|
|
0af837321c | ||
|
|
0b92440cbc | ||
|
|
24e6aca4f8 | ||
|
|
5cb0d9f455 | ||
|
|
f6fddedeae | ||
|
|
6851ef187b | ||
|
|
133019e4e5 | ||
|
|
59c4ce4c70 | ||
|
|
727a2b6cf6 | ||
|
|
1d36f49586 | ||
|
|
7480f6ab2a | ||
|
|
2207dcc248 | ||
|
|
139db488cc | ||
|
|
059e208b53 | ||
|
|
7d8e8f5b53 | ||
|
|
0a15a7651f | ||
|
|
d03a5b3fec | ||
|
|
a9d0a674f4 | ||
|
|
2152366635 | ||
|
|
ad31359fdd | ||
|
|
610e844f8e | ||
|
|
bfd93347a4 | ||
|
|
eb6395844b | ||
|
|
0286c9e705 | ||
|
|
e7a6ba07ce | ||
|
|
3144e84bd3 | ||
|
|
5f9d1daf0f | ||
|
|
9d844f8e67 | ||
|
|
303748a980 | ||
|
|
89b55047e9 | ||
|
|
0bc3f485a9 | ||
|
|
514c9555e0 | ||
|
|
94ed933a4b | ||
|
|
2cd9ed554d | ||
|
|
a881f59e9c | ||
|
|
c700ecd5f7 | ||
|
|
7bd9939acd | ||
|
|
884463917b | ||
|
|
55cc382f4b | ||
|
|
da3a3cbf7f | ||
|
|
fb02712eaf | ||
|
|
5fd7b97802 | ||
|
|
d529670ebf | ||
|
|
9cf2f879b2 | ||
|
|
e372fe335f | ||
|
|
bf0a5c2b40 | ||
|
|
db3b51fbb0 | ||
|
|
7fca01690c | ||
|
|
6ebb19bcd0 | ||
|
|
47eb39b7a5 | ||
|
|
66b9070fb2 | ||
|
|
ee2f3acb04 | ||
|
|
5aeba4fbdf | ||
|
|
c9d5e52b26 | ||
|
|
1cc3cf90fb | ||
|
|
efbcbd4691 | ||
|
|
4015922833 | ||
|
|
b74e94e4f8 | ||
|
|
0097090d64 | ||
|
|
c8ebf124b5 | ||
|
|
905fab93bd | ||
|
|
868eaefbec | ||
|
|
a15853c374 | ||
|
|
17d1a7af4c | ||
|
|
f1caf21a48 | ||
|
|
15d9d98e98 | ||
|
|
af16c671cc | ||
|
|
d0f8995829 | ||
|
|
cd3fd7ba4b | ||
|
|
3d274eb8a2 | ||
|
|
dd582da97d | ||
|
|
11ace4d8b6 | ||
|
|
dc21097da1 | ||
|
|
8bdeda36d3 | ||
|
|
323f2455b2 |
No files matched your search
@@ -2,9 +2,16 @@
|
||||
|
||||
name: Continuous Integration expired token tests
|
||||
|
||||
env:
|
||||
NODE_VERSION: 26
|
||||
JAVA_VERSION: 25
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- .github/workflows/skills-service-backward-compat.yml
|
||||
- .github/workflows/client-libs-backward-compat.yml
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -34,16 +41,16 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '19' # The JDK version to make available on the path.
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
@@ -53,7 +60,7 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -61,7 +68,7 @@ jobs:
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
@@ -70,11 +77,25 @@ jobs:
|
||||
|
||||
# - name: build skill-service
|
||||
# run: ./.github/workflows/scripts/getSkillsService.sh
|
||||
- name: download skill-service
|
||||
# - name: download skill-service
|
||||
# run: |
|
||||
# mkdir skills-service
|
||||
# cd skills-service
|
||||
# curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
# ls ./
|
||||
# cd ..
|
||||
|
||||
- name: Checkout specific branch fo skills-service
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: NationalSecurityAgency/skills-service
|
||||
path: skills-service
|
||||
|
||||
- name: build skill-service
|
||||
run: |
|
||||
mkdir skills-service
|
||||
cd skills-service
|
||||
curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
mvn --batch-mode clean install -DskipTests
|
||||
cp ./service/target/skills-service-*.jar ./
|
||||
ls ./
|
||||
cd ..
|
||||
|
||||
@@ -120,7 +141,7 @@ jobs:
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: result artifacts
|
||||
path: |
|
||||
|
||||
@@ -2,21 +2,17 @@
|
||||
|
||||
name: Continuous Integration with OAuth
|
||||
|
||||
env:
|
||||
NODE_VERSION: 26
|
||||
JAVA_VERSION: 25
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 7 * * *'
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- .github/workflows/skills-service-backward-compat.yml
|
||||
- .github/workflows/client-libs-backward-compat.yml
|
||||
workflow_dispatch:
|
||||
#on:
|
||||
# push:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
# - .github/workflows/skills-service-backward-compat.yml
|
||||
# - .github/workflows/client-libs-backward-compat.yml
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
# - .github/workflows/skills-service-backward-compat.yml
|
||||
# - .github/workflows/client-libs-backward-compat.yml
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
@@ -46,15 +42,16 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '19' # The JDK version to make available on the path.
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
@@ -64,7 +61,7 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -72,18 +69,32 @@ jobs:
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: download skill-service
|
||||
# - name: download skill-service
|
||||
# run: |
|
||||
# mkdir skills-service
|
||||
# cd skills-service
|
||||
# curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
# ls ./
|
||||
# cd ..
|
||||
|
||||
- name: Checkout specific branch fo skills-service
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: NationalSecurityAgency/skills-service
|
||||
path: skills-service
|
||||
|
||||
- name: build skill-service
|
||||
run: |
|
||||
mkdir skills-service
|
||||
cd skills-service
|
||||
curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
mvn --batch-mode clean install -DskipTests
|
||||
cp ./service/target/skills-service-*.jar ./
|
||||
ls ./
|
||||
cd ..
|
||||
|
||||
@@ -153,7 +164,7 @@ jobs:
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: result artifacts
|
||||
path: |
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
|
||||
name: Continuous Integration
|
||||
|
||||
env:
|
||||
NODE_VERSION: 26
|
||||
JAVA_VERSION: 25
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- .github/workflows/skills-service-backward-compat.yml
|
||||
- .github/workflows/client-libs-backward-compat.yml
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- .github/workflows/skills-service-backward-compat.yml
|
||||
- .github/workflows/client-libs-backward-compat.yml
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -43,15 +42,16 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '19' # The JDK version to make available on the path.
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
@@ -61,26 +61,25 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: download skill-service
|
||||
- name: Checkout specific branch fo skills-service
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: NationalSecurityAgency/skills-service
|
||||
path: skills-service
|
||||
|
||||
- name: build skill-service
|
||||
run: |
|
||||
mkdir skills-service
|
||||
cd skills-service
|
||||
curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
mvn --batch-mode clean install -DskipTests
|
||||
cp ./service/target/skills-service-*.jar ./
|
||||
ls ./
|
||||
cd ..
|
||||
|
||||
@@ -112,7 +111,7 @@ jobs:
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: result artifacts
|
||||
path: |
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Client Libs Backward Compatibility
|
||||
|
||||
#on:
|
||||
# push:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
test-client-libs-backward-compat:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: skillsPassword
|
||||
POSTGRES_DB: skills
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '19' # The JDK version to make available on the path.
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
mvn --version
|
||||
java -version
|
||||
npm -version
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Download Releases to Test
|
||||
run: |
|
||||
mkdir -p skills-service-versions
|
||||
cd skills-service-versions
|
||||
curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
ls ./
|
||||
cd ../
|
||||
|
||||
- name: setup npm links
|
||||
run: ./.github/workflows/scripts/setupNpmLinks.sh
|
||||
|
||||
- name: build integration test apps
|
||||
run: |
|
||||
cd ./skills-client-integration
|
||||
mvn --batch-mode clean install
|
||||
cd ../
|
||||
|
||||
- name: run backwards compat tests
|
||||
run: |
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
mvn --batch-mode clean package
|
||||
export NO_COLOR=1
|
||||
export CYPRESS_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.TestClientLibsBackwardsCompat org.springframework.boot.loader.launch.PropertiesLauncher -record -tag "${{ github.workflow }}" --minVersion "1.9.0"
|
||||
cd ../../
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: result artifacts
|
||||
path: |
|
||||
./skills-client-integration/skills-int-e2e-test/target/logs/*
|
||||
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
@@ -0,0 +1,197 @@
|
||||
name: Client Libs Backward Compatibility
|
||||
|
||||
env:
|
||||
NODE_VERSION: 26
|
||||
JAVA_VERSION: 25
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- .github/workflows/skills-service-backward-compat.yml
|
||||
- .github/workflows/client-libs-backward-compat.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
get_skills_service_versions:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
services: ${{ steps.generate-matrix.outputs.services }}
|
||||
versions: ${{ steps.generate-matrix.outputs.versions }}
|
||||
steps:
|
||||
- name: Generate Matrix
|
||||
id: generate-matrix
|
||||
run: |
|
||||
TO_EXCLUDE='["3.1.0","3.0.3","3.0.2","3.0.1","3.0.0"]'
|
||||
RESPONSE=$(curl -s -X GET 'https://api.github.com/repos/NationalSecurityAgency/skills-service/releases')
|
||||
ALL_TAG_NAMES=$(echo "$RESPONSE" | jq -c '[.[] | .tag_name]')
|
||||
echo "ALL_TAG_NAMES=$ALL_TAG_NAMES"
|
||||
FILTERED_TAG_NAMES=$(jq --argjson to_exclude "$TO_EXCLUDE" '. - $to_exclude' <<< "$ALL_TAG_NAMES")
|
||||
echo "FILTERED_TAG_NAMES=$FILTERED_TAG_NAMES"
|
||||
TOP_TAGS=$(jq -c '.[0:5]' <<< "$FILTERED_TAG_NAMES")
|
||||
echo "TOP_TAGS=$TOP_TAGS"
|
||||
echo ::set-output name=versions::${TOP_TAGS}
|
||||
|
||||
build_skills_client_int:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
mvn --version
|
||||
java -version
|
||||
npm -version
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: setup npm links
|
||||
run: ./.github/workflows/scripts/setupNpmLinks.sh
|
||||
|
||||
- name: build integration test apps
|
||||
env:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
run: |
|
||||
cd ./skills-client-integration
|
||||
mvn --batch-mode clean install
|
||||
cd ../
|
||||
|
||||
- name: upload service jar
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: service jar
|
||||
path: ./skills-client-integration/skills-int-service/target/skills-int-service-*.jar
|
||||
|
||||
run_backward_compatibility_tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
image: postgres
|
||||
# Provide the password for postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: skillsPassword
|
||||
POSTGRES_DB: skills
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps tcp port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
|
||||
needs:
|
||||
- get_skills_service_versions
|
||||
- build_skills_client_int
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ${{ fromJSON(needs.get_skills_service_versions.outputs.versions) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
mvn --version
|
||||
java -version
|
||||
npm -version
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: download skill-service:${{ matrix.version }}
|
||||
run: |
|
||||
mkdir skills-service
|
||||
cd skills-service
|
||||
wget https://github.com/NationalSecurityAgency/skills-service/releases/download/${{ matrix.version }}/skills-service-${{ matrix.version }}.jar
|
||||
ls ./
|
||||
cd ..
|
||||
|
||||
- name: download integration test apps
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: service jar
|
||||
path: ./skills-client-integration/skills-int-service/target/
|
||||
|
||||
- name: start services for cypress tests
|
||||
run: |
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
npm install
|
||||
npm run cyServices:start:skills-service:ci
|
||||
npm run cyServices:start:integration-apps
|
||||
cd ../../
|
||||
|
||||
|
||||
- name: Run Cypress tests
|
||||
run: |
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
npx cypress run --env skills-service.minVersion=${{ matrix.version }}
|
||||
cd ../../
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: result artifacts for ${{ matrix.version }}
|
||||
path: |
|
||||
./skills-service/*.jar
|
||||
./skills-client-integration/skills-int-service/target/*.jar
|
||||
./skills-client-integration/skills-int-e2e-test/target/logs/*.out
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
./skills-client-integration/skills-int-e2e-test/logs
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/videos
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/screenshots
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/snapshots/**/**/__diff_output__/*.png
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/snapshots/**/__diff_output__/*.png
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,11 +17,31 @@ set -e
|
||||
set -o pipefail
|
||||
|
||||
echo "------- START: Setup npm links -------"
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
mvn --batch-mode clean package -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
ls -la target/skills-int-e2e-test-*.jar
|
||||
du -sh target/*
|
||||
java --version
|
||||
java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.SetupNpmLinks org.springframework.boot.loader.launch.PropertiesLauncher
|
||||
cd ../../
|
||||
cd skills-client-js
|
||||
echo "------- skills-client-js: npm link -------"
|
||||
npm link
|
||||
cd ../skills-client-integration/skills-int-client-js/
|
||||
echo "------- skills-int-client-js: npm link @skilltree/skills-client-js -------"
|
||||
npm link @skilltree/skills-client-js
|
||||
|
||||
cd ../../skills-client-js
|
||||
echo "------- skills-client-js: npm install -------"
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
echo "------- skills-int-client-js: npm install -------"
|
||||
cd ../skills-client-integration/skills-int-client-js/
|
||||
npm install
|
||||
|
||||
echo "------- validate npm links -------"
|
||||
if ! npm ls --link --global | grep "skills-client-js"; then
|
||||
echo "Error: skills-client-js is not properly linked"
|
||||
exit 1
|
||||
else
|
||||
echo "Verified: skills-client-js is properly linked"
|
||||
fi
|
||||
|
||||
echo "------- skills-int-client-js: npm run build -------"
|
||||
npm run build
|
||||
|
||||
echo "------- DONE: Setup npm links -------"
|
||||
@@ -2,25 +2,89 @@
|
||||
|
||||
name: Skills-service Backward Compatibility
|
||||
|
||||
#on:
|
||||
# push:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - 'README.md'
|
||||
env:
|
||||
NODE_VERSION: 26
|
||||
JAVA_VERSION: 25
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * *'
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
test-skill-service-backward-compat:
|
||||
# The type of runner that the job will run on
|
||||
|
||||
build-skills-service:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
11
|
||||
${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
mvn --version
|
||||
java -version
|
||||
npm -version
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Checkout specific branch fo skills-service
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: NationalSecurityAgency/skills-service
|
||||
path: skills-service
|
||||
|
||||
- name: build skill-service
|
||||
run: |
|
||||
cd skills-service
|
||||
mvn --batch-mode clean install -DskipTests
|
||||
cd ..
|
||||
|
||||
- name: upload service jar
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: service jar
|
||||
path: ./skills-service/service/target/skills-service-*.jar
|
||||
|
||||
test-skill-service-backward-compat:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-skills-service]
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
skillsJsVersion: [ '3.5.0', '3.6.0', '3.6.1', '3.6.2', '3.6.3']
|
||||
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
@@ -42,18 +106,18 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v2-beta
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '14'
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: |
|
||||
11
|
||||
19
|
||||
${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Print Versions
|
||||
run: |
|
||||
@@ -63,7 +127,7 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -71,7 +135,7 @@ jobs:
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
- name: Cache npm
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
@@ -80,31 +144,99 @@ jobs:
|
||||
|
||||
# - name: build skill-service
|
||||
# run: ./.github/workflows/scripts/getSkillsService.sh
|
||||
- name: download skill-service
|
||||
run: |
|
||||
mkdir skills-service
|
||||
cd skills-service
|
||||
curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
ls ./
|
||||
cd ..
|
||||
# - name: download skill-service
|
||||
# run: |
|
||||
# mkdir skills-service
|
||||
# cd skills-service
|
||||
# curl -s https://api.github.com/repos/NationalSecurityAgency/skills-service/releases/latest | grep browser_download_url | cut -d '"' -f 4 | wget -qi -
|
||||
# ls ./
|
||||
# cd ..
|
||||
|
||||
- name: run backwards compat tests
|
||||
# - name: Checkout specific branch fo skills-service
|
||||
# uses: actions/checkout@v6
|
||||
# with:
|
||||
# repository: NationalSecurityAgency/skills-service
|
||||
# path: skills-service
|
||||
#
|
||||
# - name: build skill-service
|
||||
# run: |
|
||||
# cd skills-service
|
||||
# mvn --batch-mode clean install -DskipTests
|
||||
# cp ./service/target/skills-service-*.jar ./
|
||||
# ls ./
|
||||
# cd ..
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: service jar
|
||||
path: ./skills-service/
|
||||
|
||||
- name: build integration test apps
|
||||
env:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
run: |
|
||||
cd ./skills-client-integration/skills-int-client-js
|
||||
npm install @skilltree/skills-client-js@${{ matrix.skillsJsVersion }} --save-exact
|
||||
cd ..
|
||||
mvn --batch-mode clean install
|
||||
cd ../
|
||||
|
||||
- name: start services for cypress tests
|
||||
run: |
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
mvn --batch-mode clean package
|
||||
export NO_COLOR=1
|
||||
java -jar target/skills-int-e2e-test-*.jar
|
||||
npm install
|
||||
ls ../
|
||||
echo "------"
|
||||
ls ../../
|
||||
echo "------"
|
||||
ls ../../skills-service/
|
||||
echo "------"
|
||||
npm run cyServices:start:skills-service:ci
|
||||
npm run cyServices:start:integration-apps
|
||||
cd ../../
|
||||
|
||||
|
||||
- name: Run Cypress tests
|
||||
run: |
|
||||
cd skills-client-integration/skills-int-e2e-test
|
||||
npm run cy:run
|
||||
cd ../../
|
||||
|
||||
- name: upload result artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: result artifacts
|
||||
name: result artifacts for [${{ matrix.skillsJsVersion }}] version
|
||||
path: |
|
||||
./skills-client-integration/skills-int-e2e-test/target/logs/*
|
||||
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
./skills-service/*.jar
|
||||
./skills-client-integration/skills-int-service/target/*.jar
|
||||
./skills-client-integration/skills-int-e2e-test/target/logs/*.out
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
/home/runner/.npm/_logs/*
|
||||
./skills-client-integration/skills-int-e2e-test/logs
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/videos
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/screenshots
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/snapshots/**/**/__diff_output__/*.png
|
||||
./skills-client-integration/skills-int-e2e-test/cypress/snapshots/**/__diff_output__/*.png
|
||||
|
||||
|
||||
# - name: run backwards compat tests
|
||||
# run: |
|
||||
# cd skills-client-integration/skills-int-e2e-test
|
||||
# mvn --batch-mode clean package
|
||||
# export NO_COLOR=1
|
||||
# java -jar target/skills-int-e2e-test-*.jar
|
||||
# cd ../../
|
||||
#
|
||||
# - name: upload result artifacts
|
||||
# if: ${{ always() }}
|
||||
# uses: actions/upload-artifact@v7
|
||||
# with:
|
||||
# name: result artifacts
|
||||
# path: |
|
||||
# ./skills-client-integration/skills-int-e2e-test/target/logs/*
|
||||
# ./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
# ./skills-client-integration/skills-int-e2e-test/target/skills-client/skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
# ./skills-client-integration/skills-int-e2e-test/cypress/videos/*.mp4
|
||||
# ./skills-client-integration/skills-int-e2e-test/cypress/screenshots/**/*.png
|
||||
# /home/runner/.npm/_logs/*
|
||||
@@ -15,3 +15,6 @@
|
||||
/skills-client-ng/package-lock.json
|
||||
/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
|
||||
@@ -1,10 +1,16 @@
|
||||
# SkillTree Client Libraries
|
||||
<p>
|
||||
<img src="https://img.shields.io/npm/d18m/%40skilltree%2Fskills-client-js" alt="npm downloads">
|
||||
<img src="https://img.shields.io/npm/dw/%40skilltree%2Fskills-client-js" alt="npm weekly downloads">
|
||||
<img src="https://img.shields.io/npm/l/%40skilltree%2Fskills-client-js" alt="License">
|
||||
<img src="https://img.shields.io/npm/v/%40skilltree%2Fskills-client-js" alt="Latest Version">
|
||||
<img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/%40skilltree%2Fskills-client-js">
|
||||
</p>
|
||||
|
||||
SkillTree is an innovative approach to implementing application training. SkillTree skills-client project is an assembly of
|
||||
client libraries that facilitate rapid integration with the SkillTree service.
|
||||
SkillTree is an innovative approach to implementing application training. SkillTree's ``skills-client-js`` project facilitates rapid integration with the SkillTree service.
|
||||
|
||||
To learn about the SkillTree platform please visit our [Official Documentation](https://code.nsa.gov/skills-docs/).
|
||||
These pages provide in-depth guidance on the installation, usage and contribution.
|
||||
To learn about the SkillTree platform please visit our [Official Documentation](https://skilltreeplatform.dev/).
|
||||
These pages provide in-depth guidance on the installation, usage and contribution.
|
||||
|
||||
## Workflow Status
|
||||
|
||||
@@ -16,7 +22,7 @@ These pages provide in-depth guidance on the installation, usage and contributio
|
||||
|
||||
[](https://github.com/NationalSecurityAgency/skills-client/actions/workflows/skills-service-backward-compat.yml)
|
||||
|
||||
[](https://github.com/NationalSecurityAgency/skills-client/actions/workflows/client-libs-backward-compat.yml)
|
||||
[](https://github.com/NationalSecurityAgency/skills-client/actions/workflows/client-libs-backwards-compat.yml)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.3</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.5.0-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
@@ -31,9 +31,6 @@
|
||||
<properties>
|
||||
<!--suppress UnresolvedMavenProperty -->
|
||||
<nexusServer>${nexus.url}</nexusServer>
|
||||
<frontend-maven-plugin.version>1.10.3</frontend-maven-plugin.version>
|
||||
<node.version>v14.15.0</node.version>
|
||||
<java.version>19</java.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,14 +17,14 @@ limitations under the License.
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Skills Examples - Native Javascript</title>
|
||||
<title>Skill Client Test</title>
|
||||
<link rel="icon" type="image/x-icon" href="./skilltree.ico">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./assets/app.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./assets/js/bootstrap/dist/css/bootstrap.css">
|
||||
|
||||
<script type="text/javascript" src="./assets/js/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/axios/dist/axios.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/js-beautify/js/lib/beautify.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/@skilltree/skills-client-js/dist/skills-client-js.umd.min.js"></script>
|
||||
@@ -32,15 +32,19 @@ limitations under the License.
|
||||
<script type="text/javascript">
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const isSummaryOnly = params.get('isSummaryOnly') ? JSON.parse(params.get('isSummaryOnly')) : false;
|
||||
const internalBackButton = params.get('internalBackButton') ? JSON.parse(params.get('internalBackButton')) : false;
|
||||
|
||||
let clientDisplay = null;
|
||||
|
||||
const options = {
|
||||
autoScrollStrategy: 'top-of-page',
|
||||
isSummaryOnly,
|
||||
internalBackButton,
|
||||
};
|
||||
|
||||
const internalBackButtonParam = 'internalBackButton'
|
||||
if (params.get(internalBackButtonParam)) {
|
||||
options.internalBackButton = JSON.parse(params.get(internalBackButtonParam))
|
||||
}
|
||||
|
||||
const themes = {
|
||||
default: {
|
||||
name: 'Bright (default)',
|
||||
@@ -155,32 +159,38 @@ limitations under the License.
|
||||
|
||||
<div id="app">
|
||||
<div>
|
||||
<div>
|
||||
<nav class="navbar navbar-dark bg-info">
|
||||
<a href="#" class="navbar-brand">Native Javascript Integration Examples</a>
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="index.html" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;">
|
||||
<div class="container-fluid">
|
||||
<a href="#" class="navbar-brand">Client Test</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" aria-current="page" href="index.html" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="javascript:void(0)" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="container">
|
||||
<div class="">
|
||||
<div class="form-row align-items-center">
|
||||
<div class="d-inline dropdown">
|
||||
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Change Theme
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="javascript:void(0)" id="default-theme-dropdown-item" onclick="applyTheme(themes.default)">Bright (Default)</a>
|
||||
<a class="dropdown-item" href="javascript:void(0)" id="darkblue-theme-dropdown-item" onclick="applyTheme(themes.darkBlue)">Dark Blue</a>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="d-inline custom-control custom-checkbox mx-2">
|
||||
@@ -192,26 +202,19 @@ limitations under the License.
|
||||
<label class="custom-control-label" for="internalBackButtonControl">Internal Back Button</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-primary" type="button" data-cy="navigateButton" onclick="navigate()">
|
||||
<button class="btn btn-primary" type="button" data-cy="navigateButton" onclick="navigate()">
|
||||
Navigate
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="my-3">
|
||||
<span id="skillsDisplayPath" data-cy="skillsDisplayPath"></span>
|
||||
</div>
|
||||
<div
|
||||
style="min-height: 860px">
|
||||
<div id="skills-client-display" />
|
||||
<div id="skills-client-display"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-muted bg-light py-5">
|
||||
<div class="container">
|
||||
<p>Native Javascript Examples Guide</p>
|
||||
<p></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -220,5 +223,5 @@ limitations under the License.
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById("displayTypeControl").checked = isSummaryOnly;
|
||||
document.getElementById("internalBackButtonControl").checked = internalBackButton;
|
||||
document.getElementById("internalBackButtonControl").checked = params.get(internalBackButtonParam) ? JSON.parse(params.get(internalBackButtonParam)) : false
|
||||
</script>
|
||||
+7
-32
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,14 +17,14 @@ limitations under the License.
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Skills Examples - Native Javascript</title>
|
||||
<title>Skill Client Test</title>
|
||||
<link rel="icon" type="image/x-icon" href="./skilltree.ico">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./assets/app.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./assets/js/bootstrap/dist/css/bootstrap.css">
|
||||
|
||||
<script type="text/javascript" src="./assets/js/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/axios/dist/axios.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/js-beautify/js/lib/beautify.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/@skilltree/skills-client-js/dist/skills-client-js.umd.min.js"></script>
|
||||
@@ -53,37 +53,12 @@ limitations under the License.
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<div>
|
||||
<div>
|
||||
<nav class="navbar navbar-dark bg-info">
|
||||
<a href="#" class="navbar-brand">Native Javascript Integration Examples</a>
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="index.html" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="container">
|
||||
<div
|
||||
style="min-height: 860px">
|
||||
<div id="skills-client-display" />
|
||||
</div>
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="container">
|
||||
<div style="min-height: 860px">
|
||||
<div id="skills-client-display"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-muted bg-light py-5">
|
||||
<div class="container">
|
||||
<p>Native Javascript Examples Guide</p>
|
||||
<p></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<!--
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Skill Client Test</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="./skilltree.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/app.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./assets/js/bootstrap/dist/css/bootstrap.css">
|
||||
|
||||
<script type="text/javascript" src="./assets/js/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/axios/dist/axios.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
|
||||
<script type="module">
|
||||
import { SkillsReporter, SkillsConfiguration, SkillsLevelJS, SkillsDisplayJS, Logger } from './assets/js/@skilltree/skills-client-js/dist/skills-client-js.esm.min.js'
|
||||
|
||||
axios.get("/api/config")
|
||||
.then((result) => {
|
||||
SkillsConfiguration.configure({...result.data, enabled: false});
|
||||
});
|
||||
|
||||
document.querySelector('#reportSkillBtn').addEventListener('click', () => {
|
||||
SkillsReporter.reportSkill('someId')
|
||||
});
|
||||
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
const initializeSkillsDisplay = () => {
|
||||
const clientDisplay = new SkillsDisplayJS();
|
||||
clientDisplay.attachTo(document.querySelector('#skills-client-display'));
|
||||
};
|
||||
if (document.readyState === "complete"
|
||||
|| document.readyState === "loaded"
|
||||
|| document.readyState === "interactive") {
|
||||
initializeSkillsDisplay();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", initializeSkillsDisplay);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<button
|
||||
id="reportSkillBtn"
|
||||
data-cy="reportSkillButton"
|
||||
class="btn btn-outline-primary">
|
||||
Report Skill
|
||||
</button>
|
||||
|
||||
<div data-cy="levelComponentRes">Level: [<span id="skillLevel"/>]</div>
|
||||
<div data-cy="skillsDisplayRes">Skills Display: [<div id="skills-client-display"></div>]</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,13 +17,15 @@ limitations under the License.
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Skills Examples - Native Javascript</title>
|
||||
<title>Skill Client Test</title>
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="./skilltree.ico">
|
||||
<link rel="stylesheet" type="text/css" href="./assets/app.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="./assets/js/bootstrap/dist/css/bootstrap.css">
|
||||
|
||||
<script type="text/javascript" src="./assets/js/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/axios/dist/axios.min.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/js-beautify/js/lib/beautify.js"></script>
|
||||
<script type="text/javascript" src="./assets/js/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
|
||||
<script type="module">
|
||||
import { SkillsReporter, SkillsConfiguration, SkillsLevelJS, Logger } from './assets/js/@skilltree/skills-client-js/dist/skills-client-js.esm.min.js'
|
||||
@@ -32,43 +34,43 @@ limitations under the License.
|
||||
.then((result) => {
|
||||
SkillsConfiguration.configure(result.data);
|
||||
});
|
||||
|
||||
SkillsReporter.addSuccessHandler((result) => {
|
||||
document.querySelector('#global-event-result-container-js').innerHTML = JSON.stringify(result, null, 2);
|
||||
});
|
||||
|
||||
SkillsReporter.addErrorHandler((result) => {
|
||||
document.querySelector('#global-event-result-container-js').innerHTML = JSON.stringify(result, null, 2);
|
||||
});
|
||||
|
||||
const reportSkill = (skillId) => {
|
||||
SkillsReporter.reportSkill(skillId)
|
||||
.then((response) => {
|
||||
document.querySelector('#report-result-container').innerHTML = JSON.stringify(response, null, 2);
|
||||
}).catch((err) => {
|
||||
document.querySelector('#report-result-container').innerHTML = JSON.stringify(err, null, 2);
|
||||
});
|
||||
};
|
||||
|
||||
document.querySelector('#exampleDirectiveClickEvent').addEventListener('click', () => {
|
||||
var skillId = document.getElementById('skillId').value;
|
||||
reportSkill(skillId);
|
||||
});
|
||||
|
||||
const toggleAllEvents = () => {
|
||||
const notifyIfSkillNotApplied = document.querySelector('#all-events-check').checked
|
||||
SkillsReporter.configure( { notifyIfSkillNotApplied });
|
||||
}
|
||||
document.querySelector('#all-events-check').addEventListener('click', toggleAllEvents);
|
||||
toggleAllEvents();
|
||||
SkillsConfiguration.afterConfigure()
|
||||
.then(() => {
|
||||
if (window.Cypress) {
|
||||
window.skillsLogger = Logger;
|
||||
}
|
||||
const skillLevel = new SkillsLevelJS();
|
||||
skillLevel.attachTo('#skillLevel')
|
||||
});
|
||||
.then(() => {
|
||||
SkillsReporter.addSuccessHandler((result) => {
|
||||
document.querySelector('#global-event-result-container-js').innerHTML = JSON.stringify(result, null, 2);
|
||||
});
|
||||
|
||||
SkillsReporter.addErrorHandler((result) => {
|
||||
document.querySelector('#global-event-result-container-js').innerHTML = JSON.stringify(result, null, 2);
|
||||
});
|
||||
|
||||
const reportSkill = (skillId) => {
|
||||
SkillsReporter.reportSkill(skillId)
|
||||
.then((response) => {
|
||||
document.querySelector('#report-result-container').innerHTML = JSON.stringify(response, null, 2);
|
||||
}).catch((err) => {
|
||||
document.querySelector('#report-result-container').innerHTML = JSON.stringify(err, null, 2);
|
||||
});
|
||||
};
|
||||
|
||||
document.querySelector('#exampleDirectiveClickEvent').addEventListener('click', () => {
|
||||
var skillId = document.getElementById('skillId').value;
|
||||
reportSkill(skillId);
|
||||
});
|
||||
|
||||
const toggleAllEvents = () => {
|
||||
const notifyIfSkillNotApplied = document.querySelector('#all-events-check').checked
|
||||
SkillsReporter.configure({notifyIfSkillNotApplied});
|
||||
}
|
||||
document.querySelector('#all-events-check').addEventListener('click', toggleAllEvents);
|
||||
toggleAllEvents();
|
||||
|
||||
if (window.Cypress) {
|
||||
window.skillsLogger = Logger;
|
||||
}
|
||||
const skillLevel = new SkillsLevelJS();
|
||||
skillLevel.attachTo('#skillLevel')
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
@@ -77,20 +79,26 @@ limitations under the License.
|
||||
<div id="app">
|
||||
<div>
|
||||
<div>
|
||||
<nav class="navbar navbar-dark bg-info">
|
||||
<a href="#" class="navbar-brand">Native Javascript Integration Examples</a>
|
||||
|
||||
<ul class="navbar-nav flex-row mr-auto">
|
||||
<li class="nav-item active pr-3">
|
||||
<a class="nav-link" href="javascript:void(0)" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item pr-3">
|
||||
<a class="nav-link" href="clientDisplay.html" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary">
|
||||
<span id="skillLevel"/>
|
||||
</button>
|
||||
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;">
|
||||
<div class="container-fluid">
|
||||
<a href="#" class="navbar-brand">Client Test</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="javascript:void(0)" data-cy="reportSkillsLink">Report Skill Events</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="clientDisplay.html" data-cy="userDisplayLink">User Display</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary">
|
||||
<span id="skillLevel"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -140,13 +148,6 @@ limitations under the License.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-muted bg-light py-5">
|
||||
<div class="container">
|
||||
<p>Native Javascript Examples Guide</p>
|
||||
<p></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skills-int-client-js",
|
||||
"version": "3.5.0",
|
||||
"version": "3.6.5",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib Integration Tests - Native JS",
|
||||
"main": "index.js",
|
||||
@@ -12,20 +12,19 @@
|
||||
"licenseHeaderCheck": "license-check-and-add check -f '../license-add/license-add-config.json'",
|
||||
"licenseHeaderAdd": "license-check-and-add add -f ../license-add/license-add-config.json",
|
||||
"licenseCheck": "npm run licenseHeaderCheck && npm run licenseDepCheck",
|
||||
"build": "npm run licenseCheck && echo nothingelsetodo"
|
||||
"build": "npm install && npm run licenseCheck && echo nothingelsetodo"
|
||||
},
|
||||
"author": "SkillTree Team",
|
||||
"dependencies": {
|
||||
"@skilltree/skills-client-js": "3.5.0",
|
||||
"axios": "1.7.4",
|
||||
"bootstrap": "4.5.2",
|
||||
"jquery": "3.5.1",
|
||||
"js-beautify": "1.13.0"
|
||||
"@skilltree/skills-client-js": "3.6.5",
|
||||
"axios": "1.20.0",
|
||||
"bootstrap": "5.3.8",
|
||||
"jquery": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"fs-extra": "8.1.0",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"fs-extra": "11.4.0",
|
||||
"license-check-and-add": "4.0.5",
|
||||
"license-checker": "25.0.1",
|
||||
"live-server": "1.2.1"
|
||||
"live-server": "1.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.5.0-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-client-js</artifactId>
|
||||
|
||||
@@ -19,55 +19,26 @@
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>${frontend-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<installDirectory>target</installDirectory>
|
||||
</configuration>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<id>exec-npm-install</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>${node.version}</nodeVersion>
|
||||
<executable>npm</executable>
|
||||
<arguments>
|
||||
<argument>run</argument>
|
||||
<argument>build</argument>
|
||||
</arguments>
|
||||
<workingDirectory>${basedir}</workingDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<arguments>--unsafe-perm install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run build</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<!--TODO: uncomment when we have at least 1 unit test-->
|
||||
<!--<execution>-->
|
||||
<!--<id>npm run test</id>-->
|
||||
<!--<goals>-->
|
||||
<!--<goal>npm</goal>-->
|
||||
<!--</goals>-->
|
||||
<!--<phase>test</phase>-->
|
||||
<!--<configuration>-->
|
||||
<!--<arguments>run unit</arguments>-->
|
||||
<!--</configuration>-->
|
||||
<!--</execution>-->
|
||||
</executions>
|
||||
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
projectId: '3yas4t',
|
||||
video: true,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,10 @@ export default {
|
||||
const versionToCompare = Cypress.env(lib);
|
||||
return this.laterThanMinVersion(minVersion, versionToCompare);
|
||||
},
|
||||
skillsClientJSVersionLaterThan(minVersion) {
|
||||
const versionToCompare = Cypress.env('skills-client-js-version')
|
||||
return this.laterThanMinVersion(minVersion, versionToCompare);
|
||||
},
|
||||
skillsServiceVersionLaterThan(minVersion) {
|
||||
const versionToCompare = Cypress.env('skills-service.minVersion');
|
||||
return this.laterThanMinVersion(minVersion, versionToCompare);
|
||||
@@ -30,5 +34,20 @@ export default {
|
||||
}
|
||||
const shouldSkip = semver.lt(currentVersion, minVersion);
|
||||
return !shouldSkip;
|
||||
}
|
||||
},
|
||||
laterThan_3_1_0() {
|
||||
return this.skillsServiceVersionLaterThan('3.1.0');
|
||||
},
|
||||
laterThan_1_4_0() {
|
||||
return this.skillsServiceVersionLaterThan('1.4.0');
|
||||
},
|
||||
laterThan_1_11_1() {
|
||||
return this.skillsServiceVersionLaterThan('1.11.1');
|
||||
},
|
||||
noThemeBackground() {
|
||||
return 'rgb(255, 255, 255)';
|
||||
},
|
||||
rankDetailsTitle() {
|
||||
return this.laterThan_1_11_1() ? 'My Rank' : 'Rank Overview'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 Utils from "./Utils";
|
||||
import selectors from "../support/selectors";
|
||||
|
||||
context("Configuration Disabled Tests", () => {
|
||||
|
||||
it('export user transcript', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.6.0'))
|
||||
cy.createDefaultTinyProject('proj1')
|
||||
|
||||
// visit client display
|
||||
cy.intercept('**/public/status', cy.spy().as('getStatus'))
|
||||
cy.intercept('POST', '**/api/projects/proj1/skillsClientVersion', cy.spy().as('reportClientVersion'))
|
||||
cy.intercept('**/api/projects/proj1/level', cy.spy().as('getLevel'))
|
||||
cy.intercept('**/skills-websocket/info**', cy.spy().as('initWebsockets'))
|
||||
|
||||
cy.intercept('POST', '** /api/projects/proj1/skills/someId', cy.spy().as('reportSkillId'))
|
||||
cy.intercept('/api/projects/proj1/summary', cy.spy().as('getProjSummary'))
|
||||
|
||||
cy.visit('/native/disabledConfig.html');
|
||||
cy.wait(5000)
|
||||
cy.get('@getStatus').should('not.have.been.called');
|
||||
cy.get('@reportClientVersion').should('not.have.been.called');
|
||||
cy.get('@getLevel').should('not.have.been.called');
|
||||
cy.get('@initWebsockets').should('not.have.been.called');
|
||||
cy.get('[data-cy="levelComponentRes"]').contains('Level: []')
|
||||
|
||||
cy.get('[data-cy="skillsDisplayRes"] #skills-client-display').should('be.empty')
|
||||
cy.get('@getProjSummary').should('not.have.been.called');
|
||||
|
||||
cy.get('[data-cy="reportSkillButton"]').click()
|
||||
cy.wait(3000)
|
||||
cy.get('@reportSkillId').should('not.have.been.called');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 Utils from "./Utils";
|
||||
import selectors from "../support/selectors";
|
||||
|
||||
const homePage = '/native/index.html'
|
||||
|
||||
context("Global Events Tests", () => {
|
||||
|
||||
const level1CompleteRegex = /"completed"\s*:\s*\[(?=[\s\S]*?\{[^{}]*?(?:"type"\s*:\s*"Overall"[^{}]*?"level"\s*:\s*1|"level"\s*:\s*1[^{}]*?"type"\s*:\s*"Overall")[^{}]*?\})[\s\S]*?\]/;
|
||||
|
||||
it("global event show correct results", () => {
|
||||
cy.createDefaultProject();
|
||||
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.clickSubmit();
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(level1CompleteRegex)
|
||||
});
|
||||
it("global event show correct results (skills reported directly to backend endpoint)", () => {
|
||||
cy.createDefaultProject();
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser("IronMan", Cypress.env('proxyUser'));
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.visible')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(level1CompleteRegex)
|
||||
});
|
||||
|
||||
it('global event does not update when skill reported for a different project', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.createDefaultTinyProject('proj2')
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
it('global event is not reported when skill is not applied (skill reported directly to backend endpoint)', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.get('[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
it('global event is not reported when skill is not applied', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
cy.get('[type="checkbox"]').uncheck()
|
||||
cy.clickSubmit()
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
|
||||
it('global event is reported even when skill is not applied when notifyIfSkillNotApplied=true ', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.get('[type="checkbox"]').check()
|
||||
cy.clickSubmit()
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 0')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": false')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"explanation": "This skill reached its maximum points"')
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,96 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Utils from "./Utils";
|
||||
import selectors from "../support/selectors";
|
||||
|
||||
const homePage = '/native/index.html'
|
||||
|
||||
context("Native JS Tests", () => {
|
||||
|
||||
const laterThan_1_4_0 = Utils.skillsServiceVersionLaterThan('1.4.0');
|
||||
const laterThan_1_11_1 = Utils.skillsServiceVersionLaterThan('1.11.1');
|
||||
const noThemeBackground = laterThan_1_4_0 ? 'rgba(0, 0, 0, 0)' : 'rgb(255, 255, 255)';
|
||||
const rankDetailsTitle = laterThan_1_11_1 ? 'My Rank' : 'Rank Overview'
|
||||
|
||||
it("global event show correct results", () => {
|
||||
cy.createDefaultProject();
|
||||
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.clickSubmit();
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(/completed": [[][^]*"type": "Overall",[^]\s*"level": 1/)
|
||||
});
|
||||
it("global event show correct results (skills reported directly to backend endpoint)", () => {
|
||||
cy.createDefaultProject();
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser("IronMan", Cypress.env('proxyUser'));
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.visible')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(/completed": [[][^]*"type": "Overall",[^]\s*"level": 1/)
|
||||
});
|
||||
|
||||
it('global event does not update when skill reported for a different project', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.createDefaultTinyProject('proj2')
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'), 'proj2')
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
it('global event is not reported when skill is not applied (skill reported directly to backend endpoint)', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.get('[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
it('global event is not reported when skill is not applied', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
cy.get('[type="checkbox"]').uncheck()
|
||||
cy.clickSubmit()
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
|
||||
})
|
||||
|
||||
it('global event is reported even when skill is not applied when notifyIfSkillNotApplied=true ', () => {
|
||||
cy.createDefaultProject()
|
||||
cy.reportSkillForUser('IronMan', Cypress.env('proxyUser'))
|
||||
|
||||
const sendEventViaDropdownId = "#exampleDirectiveClickEvent";
|
||||
Cypress.Commands.add("clickSubmit", () => {
|
||||
cy.get(`${sendEventViaDropdownId} .btn`).click();
|
||||
});
|
||||
|
||||
cy.visitHomePage(homePage);
|
||||
cy.get('[type="checkbox"]').check()
|
||||
cy.clickSubmit()
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 0')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": false')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"explanation": "This skill reached its maximum points"')
|
||||
})
|
||||
context("Skills Display Tests", () => {
|
||||
|
||||
it("skill display", () => {
|
||||
cy.createDefaultTinyProject();
|
||||
@@ -113,7 +28,7 @@ context("Native JS Tests", () => {
|
||||
userId: Cypress.env('proxyUser'),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
cy.visit("/native/clientDisplay.html");
|
||||
cy.visitSkillsDisplay();
|
||||
|
||||
cy.wrapIframe().contains('My Level')
|
||||
cy.wrapIframe().contains('50 Points earned Today')
|
||||
@@ -121,8 +36,8 @@ context("Native JS Tests", () => {
|
||||
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
cy.wrapIframe().find('.skills-page-title-text-color')
|
||||
.should('have.css', 'background-color').and('equal', noThemeBackground);
|
||||
cy.wrapIframe().find('[data-cy="skillsTitle"]')
|
||||
.should('have.css', 'background-color').and('equal', Utils.noThemeBackground());
|
||||
});
|
||||
|
||||
it("skill display - default options", () => {
|
||||
@@ -139,11 +54,12 @@ context("Native JS Tests", () => {
|
||||
cy.wrapIframe().contains('My Level')
|
||||
cy.wrapIframe().contains('50 Points earned Today')
|
||||
cy.wrapIframe().contains('Subject 0')
|
||||
cy.wrapIframe().find(selectors.firstSubjectTileBtn)
|
||||
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
cy.wrapIframe().find('.skills-page-title-text-color')
|
||||
.should('have.css', 'background-color').and('equal', noThemeBackground);
|
||||
cy.wrapIframe().find('[data-cy="skillsTitle"]')
|
||||
.should('have.css', 'background-color').and('equal', Utils.noThemeBackground());
|
||||
});
|
||||
|
||||
it("skill display - summary only", () => {
|
||||
@@ -154,17 +70,17 @@ context("Native JS Tests", () => {
|
||||
userId: Cypress.env('proxyUser'),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
cy.visit("/native/clientDisplay.html?isSummaryOnly=true");
|
||||
cy.visitSkillsDisplay("?isSummaryOnly=true");
|
||||
|
||||
|
||||
cy.wrapIframe().contains('My Level')
|
||||
cy.wrapIframe().contains('50 Points earned Today')
|
||||
cy.wrapIframe().contains('Subject 0').should('not.exist')
|
||||
cy.wrapIframe().find('[data-cy="subjectTile-subj0"] [data-cy="subjectTileBtn"]').should('not.exist')
|
||||
|
||||
// verify that there is no background set
|
||||
// cypress always validates against rgb
|
||||
cy.wrapIframe().find('.skills-page-title-text-color')
|
||||
.should('have.css', 'background-color').and('equal', noThemeBackground);
|
||||
cy.wrapIframe().find('[data-cy="skillsTitle"]')
|
||||
.should('have.css', 'background-color').and('equal', Utils.noThemeBackground());
|
||||
});
|
||||
|
||||
it("skill display - theme", () => {
|
||||
@@ -175,7 +91,7 @@ context("Native JS Tests", () => {
|
||||
userId: Cypress.env('proxyUser'),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
cy.visit("/native/clientDisplay.html?themeName=Dark Blue");
|
||||
cy.visitSkillsDisplay("?themeName=Dark Blue");
|
||||
cy.wait("@getToken");
|
||||
|
||||
cy.wrapIframe().contains('My Level')
|
||||
@@ -184,7 +100,7 @@ context("Native JS Tests", () => {
|
||||
|
||||
// verify dark blue background of hex #152E4d
|
||||
// cypress always validates against rgb
|
||||
cy.wrapIframe().find('.skills-page-title-text-color')
|
||||
cy.wrapIframe().find('[data-cy="skillsTitle"]')
|
||||
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
|
||||
});
|
||||
|
||||
@@ -202,50 +118,54 @@ context("Native JS Tests", () => {
|
||||
|
||||
cy.wrapIframe().contains('My Level')
|
||||
cy.wrapIframe().contains('50 Points earned Today')
|
||||
cy.wrapIframe().contains('Subject 0').should('not.exist')
|
||||
cy.wrapIframe().find('[data-cy="subjectTile-subj0"] [data-cy="subjectTileBtn"]').should('not.exist')
|
||||
|
||||
// verify dark blue background of hex #152E4d
|
||||
// cypress always validates against rgb
|
||||
cy.wrapIframe().find('.skills-page-title-text-color')
|
||||
cy.wrapIframe().find('[data-cy="skillsTitle"]')
|
||||
.should('have.css', 'background-color').and('equal', 'rgb(21, 46, 77)');
|
||||
});
|
||||
|
||||
it('client display should display an error if skills service is down', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.2.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.intercept('/public/status',
|
||||
{
|
||||
statusCode: 503, // server is down
|
||||
body: {}
|
||||
}).as('getStatus')
|
||||
cy.visit('/native/clientDisplay.html');
|
||||
cy.visitSkillsDisplay();
|
||||
cy.wait('@getStatus')
|
||||
|
||||
cy.contains("Could NOT reach Skilltree Service");
|
||||
});
|
||||
|
||||
it("only display skills up-to the provided version", () => {
|
||||
const noVersionPoints = 'Earn up to 200 points';
|
||||
const v1Points = 'Earn up to 150 points';
|
||||
const v0Points = 'Earn up to 100 points';
|
||||
const totalPointsSelector = '[data-cy="totalPoints"]'
|
||||
cy.createDefaultTinyProject();
|
||||
cy.intercept(Cypress.env('tokenUrl'))
|
||||
.as("getToken");
|
||||
cy.backendAddSkill("skillv1", 1);
|
||||
cy.backendAddSkill("skillv2", 2);
|
||||
cy.visit("/native/clientDisplay.html");
|
||||
cy.visitSkillsDisplay();
|
||||
cy.wait("@getToken");
|
||||
cy.wait('@getToken')
|
||||
cy.wrapIframe().contains(noVersionPoints);
|
||||
const validateTotalPoints = (expectedPoints) => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')){
|
||||
cy.wrapIframe().find(totalPointsSelector).should('have.text', `${expectedPoints}`);
|
||||
} else {
|
||||
cy.wrapIframe().find('.user-skills-overview').contains(`Earn up to ${expectedPoints} points`)
|
||||
}
|
||||
}
|
||||
validateTotalPoints(200)
|
||||
|
||||
// cy.visit("/native/index.html");
|
||||
cy.visit("/native/clientDisplay.html?skillsVersion=1");
|
||||
cy.visitSkillsDisplay("?skillsVersion=1");
|
||||
cy.wait('@getToken')
|
||||
cy.wrapIframe().contains(v1Points);
|
||||
validateTotalPoints(150)
|
||||
|
||||
// cy.visit("/native/index.html");
|
||||
cy.visit("/native/clientDisplay.html?skillsVersion=0");
|
||||
cy.visitSkillsDisplay("?skillsVersion=0");
|
||||
cy.wait('@getToken')
|
||||
cy.wrapIframe().contains(v0Points);
|
||||
validateTotalPoints(100)
|
||||
});
|
||||
|
||||
it('skillsClientVersion is reported correctly', () => {
|
||||
@@ -290,171 +210,4 @@ context("Native JS Tests", () => {
|
||||
cy.contains('Level 1')
|
||||
})
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.5.0')) {
|
||||
it('internal back button when when returning from an external page', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
// cy.wait('@getToken')
|
||||
|
||||
cy.clientDisplay(true).find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// now visit the "Report Skills" (external) page
|
||||
cy.get('[data-cy=reportSkillsLink]').click()
|
||||
cy.contains('Report Skills Examples');
|
||||
|
||||
// switch back to the the client display
|
||||
cy.get('[data-cy=userDisplayLink]').click()
|
||||
});
|
||||
|
||||
it('internal back button when when returning from an external page - multiple layers deep', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
cy.clientDisplay().find('[data-cy=back]').should('exist');
|
||||
|
||||
// click the back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.cdBack('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.cdBack('User Skills');
|
||||
});
|
||||
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visit('/native/clientDisplay.html?skillsClientDisplayPath=%2Frank#/showSkills');
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
cy.clientDisplay().find('[data-cy=back]').should('not.exist');
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find('[data-cy=myRank]').click();
|
||||
cy.clientDisplay().contains(rankDetailsTitle);
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('1.6.0')) {
|
||||
it('navigate skills-display programatically', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit('/native/clientDisplay.html?internalBackButton=true');
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/]');
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 Utils from "./Utils";
|
||||
import selectors from "../support/selectors";
|
||||
|
||||
context("Navigation Tests", () => {
|
||||
|
||||
it('internal back button when when returning from an external page', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.intercept(Cypress.env('tokenUrl')).as('getToken')
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
const url = Utils.skillsClientJSVersionLaterThan('3.3.0') ? '?internalBackButton=true' : ''
|
||||
cy.visitSkillsDisplay(url);
|
||||
|
||||
// cy.clientDisplay(true)
|
||||
cy.wrapIframe().find(selectors.myRankButton)
|
||||
cy.wrapIframe().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.wrapIframe().find(selectors.myRankButton).click();
|
||||
cy.wrapIframe().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
cy.wrapIframe().find(selectors.titleSection).find(selectors.backButton).should('exist')
|
||||
|
||||
// now visit the "Report Skills" (external) page
|
||||
cy.get('[data-cy=reportSkillsLink]').click()
|
||||
cy.contains('Report Skills Examples');
|
||||
|
||||
// switch back to the the client display
|
||||
cy.get('[data-cy=userDisplayLink]').click()
|
||||
});
|
||||
|
||||
it('internal back button when when returning from an external page - multiple layers deep', () => {
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
const url = Utils.skillsClientJSVersionLaterThan('3.3.0') ? '?internalBackButton=true' : ''
|
||||
cy.visitSkillsDisplay(url);
|
||||
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj();
|
||||
|
||||
// navigate to Rank Overview that contains the back button
|
||||
cy.clientDisplay().find(selectors.myRankButton).click();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
cy.clientDisplay().find(selectors.backButton).should('exist');
|
||||
|
||||
// click the back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.cdBack('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.cdBack('User Skills');
|
||||
});
|
||||
|
||||
it('browser back button works correctly when internal back button is not present', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj();
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find(selectors.myRankButton).click()
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
});
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('3.1.1')) {
|
||||
it('browser back and forward operations', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html");
|
||||
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj();
|
||||
|
||||
// to skill
|
||||
cy.wrapIframe().find(selectors.buildSkillLinkSelector('Thor')).click()
|
||||
cy.wrapIframe().find(selectors.titleSection).contains('Skill Overview')
|
||||
cy.wrapIframe().find(selectors.buildSkillTitleSelector('Thor'))
|
||||
|
||||
// // click next
|
||||
cy.wrapIframe().find(selectors.nextSkillButton).click()
|
||||
cy.wrapIframe().find(selectors.buildSkillTitleSelector('IronMan'))
|
||||
|
||||
cy.go('back')
|
||||
cy.wrapIframe().find(selectors.buildSkillTitleSelector('Thor'))
|
||||
|
||||
cy.go('back')
|
||||
cy.wrapIframe().find(selectors.titleSection).contains('Subject 0')
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.go('back')
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.go('forward')
|
||||
cy.wrapIframe().find(selectors.titleSection).contains('Subject 0')
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
// cy.go('forward')
|
||||
// cy.wrapIframe().find(selectors.titleSection).contains('Skill Overview')
|
||||
// cy.wrapIframe().find(selectors.buildSkillTitleSelector('Thor'))
|
||||
//
|
||||
// cy.go('back')
|
||||
// cy.wrapIframe().find(selectors.titleSection).contains('Subject 0')
|
||||
// cy.clientDisplay().find(selectors.myRankButton)
|
||||
//
|
||||
// cy.wrapIframe().find(selectors.myRankButton).click();
|
||||
// cy.wrapIframe().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
//
|
||||
// cy.go('back')
|
||||
// cy.wrapIframe().find(selectors.titleSection).contains('Subject 0')
|
||||
// cy.clientDisplay().find(selectors.myRankButton)
|
||||
//
|
||||
// cy.go('back')
|
||||
// cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
// cy.clientDisplay().find(selectors.myRankButton)
|
||||
});
|
||||
}
|
||||
it('breadcrumb-based navigation', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?skillsClientDisplayPath=%2Fsubjects%2Fsubj0%2Frank");
|
||||
cy.wrapIframe().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
|
||||
cy.wrapIframe().find(selectors.buildBreadcrumbLinkSelector('subj0')).click()
|
||||
cy.wrapIframe().find(selectors.titleSection).contains('Subject 0')
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.wrapIframe().find(selectors.buildBreadcrumbLinkSelector('Overview')).click()
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
});
|
||||
|
||||
it('deep link and reload', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// navigate to Rank Overview via direct link
|
||||
cy.visitSkillsDisplay('?skillsClientDisplayPath=%2Frank#/showSkills');
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
|
||||
// reload and confirm we are still on Rank Overview page
|
||||
cy.reload();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
});
|
||||
|
||||
it('back button after reload', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visit("/native/clientDisplay.html?internalBackButton=false");
|
||||
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj();
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contain the internal back button
|
||||
cy.clientDisplay().find(selectors.myRankButton).click();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
cy.clientDisplay().find(selectors.titleSection).find(selectors.backButton).should('not.exist')
|
||||
|
||||
cy.reload();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
cy.clientDisplay().contains(Utils.rankDetailsTitle());
|
||||
|
||||
// click the browser back button and verify that we are still in the
|
||||
// client display (Subject page)
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('Subject 0');
|
||||
|
||||
// then back one more time and we should be back on the client display home page
|
||||
cy.wait(1000);
|
||||
cy.go('back') // browser back button
|
||||
cy.wait(1000);
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
});
|
||||
|
||||
it('route change is passed to the client app', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.0'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visitSkillsDisplay('?internalBackButton=true');
|
||||
|
||||
// to subject page
|
||||
cy.cdClickSubj();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
|
||||
// navigate to Rank Overview and that it does NOT contains the internal back button
|
||||
cy.clientDisplay().find(selectors.myRankButton).click();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains(Utils.rankDetailsTitle());
|
||||
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0/rank]');
|
||||
});
|
||||
|
||||
if (Utils.skillsServiceVersionLaterThan('3.1.1')) {
|
||||
it('navigate skills-display programmatically', () => {
|
||||
cy.onlyOn(Utils.skillsClientJSVersionLaterThan('3.3.1'))
|
||||
cy.createDefaultTinyProject()
|
||||
cy.backendPost('/api/projects/proj1/skills/Thor', {userId: Cypress.env('proxyUser'), timestamp: Date.now()})
|
||||
|
||||
// visit client display
|
||||
cy.visitSkillsDisplay('?internalBackButton=true');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
// to subject page
|
||||
cy.get('[data-cy=navigateButton]').click();
|
||||
cy.get('[data-cy=skillsDisplayPath]').contains('Skills Display Path: [/subjects/subj0]');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ const homePage = '/native/index.html'
|
||||
|
||||
context("Token timeout JS Tests", () => {
|
||||
|
||||
const level1CompleteRegex = /"completed"\s*:\s*\[(?=[\s\S]*?\{[^{}]*?(?:"type"\s*:\s*"Overall"[^{}]*?"level"\s*:\s*1|"level"\s*:\s*1[^{}]*?"type"\s*:\s*"Overall")[^{}]*?\})[\s\S]*?\]/;
|
||||
|
||||
if (Cypress.env('timeout') === true || Cypress.env('timeout') === 'true') {
|
||||
it("report skill with expired token, retry checker will resend with new token", () => {
|
||||
cy.createDefaultProject();
|
||||
@@ -28,14 +30,17 @@ context("Token timeout JS Tests", () => {
|
||||
cy.wait(71000) // wait 71 seconds to ensure the token has expired
|
||||
cy.get('[data-cy="exampleDirectiveClickEventButton"]').click();
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').should('be.empty');
|
||||
// should be empty string or empty JSON object
|
||||
cy.get('pre[data-cy=globalEventResult]')
|
||||
.invoke('text')
|
||||
.should('match', /^\s*$|^\s*\{\s*\}\s*$/);
|
||||
|
||||
cy.wait("@getToken", { timeout: 60000 }); // wait ~ another 50 seconds to all for the RetryChecker to resubmit
|
||||
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillId": "IronMan"')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"pointsEarned": 50')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains('"skillApplied": true')
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(/completed": [[][^]*"type": "Overall",[^]\s*"level": 1/)
|
||||
cy.get('pre[data-cy=globalEventResult]').contains(level1CompleteRegex)
|
||||
});
|
||||
|
||||
it('skill display - navigate after token expires', () => {
|
||||
@@ -49,7 +54,7 @@ context("Token timeout JS Tests", () => {
|
||||
|
||||
// to subject page
|
||||
cy.wait(10000) // wait 10 seconds to ensure the token has expired
|
||||
cy.cdClickSubj(0, 'Subject 0');
|
||||
cy.cdClickSubj();
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 Utils from "./Utils";
|
||||
import selectors from "../support/selectors";
|
||||
|
||||
|
||||
context("Transcript Export Tests", () => {
|
||||
|
||||
const clean = (text) => {
|
||||
return text.replace(/\n/g, '')
|
||||
}
|
||||
|
||||
let userName
|
||||
beforeEach(() => {
|
||||
userName = Cypress.env('oauthMode') ? 'foo bar (foo)' : 'user1'
|
||||
const getFileName = (projName) => {
|
||||
return `./cypress/downloads/${projName} - ${userName} - Transcript.pdf`
|
||||
}
|
||||
Cypress.Commands.add("readTranscript", (pathToPdf) => {
|
||||
cy.log(`Reading transcript [${pathToPdf}]`)
|
||||
cy.readFile(pathToPdf, { timeout: 10000 }).then(() => {
|
||||
// file exists and was successfully read
|
||||
cy.log(`Transcript [${pathToPdf}] Found!`)
|
||||
})
|
||||
return cy.task('readPdf', pathToPdf)
|
||||
});
|
||||
Cypress.Commands.add("readTranscriptForProj", (projName) => {
|
||||
const pathToPdf = getFileName(projName)
|
||||
return cy.readTranscript(pathToPdf)
|
||||
});
|
||||
})
|
||||
if (Utils.skillsServiceVersionLaterThan('3.1.0')) {
|
||||
const expectedHeaderAndFooter = 'For All Dragons enjoyment'
|
||||
const expectedHeaderAndFooterCommunityProtected = 'For Divine Dragon enjoyment'
|
||||
|
||||
it('export user transcript', () => {
|
||||
const projName = 'Export1'
|
||||
cy.createDefaultTinyProject('proj1', projName)
|
||||
|
||||
// visit client display
|
||||
cy.visitSkillsDisplay();
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.wrapIframe().find('[data-cy="downloadTranscriptBtn"]').click()
|
||||
|
||||
cy.readTranscriptForProj(projName).then((doc) => {
|
||||
expect(doc.numpages).to.equal(2)
|
||||
expect(clean(doc.text)).to.include('SkillTree Transcript')
|
||||
expect(clean(doc.text)).to.include(projName)
|
||||
expect(clean(doc.text)).to.include('Level: 0 / 5 ')
|
||||
expect(clean(doc.text)).to.include('Points: 0 / 100 ')
|
||||
expect(clean(doc.text)).to.include('Skills: 0 / 2 ')
|
||||
expect(clean(doc.text)).to.include(userName)
|
||||
expect(clean(doc.text)).to.not.include('Badges')
|
||||
|
||||
// should be a title on the 2nd page
|
||||
expect(clean(doc.text)).to.include('Subject: Subject 0'.toUpperCase())
|
||||
|
||||
expect(clean(doc.text)).to.not.include(expectedHeaderAndFooter)
|
||||
expect(clean(doc.text)).to.not.include(expectedHeaderAndFooterCommunityProtected)
|
||||
expect(clean(doc.text)).to.not.include('null')
|
||||
})
|
||||
});
|
||||
|
||||
it('export user transcript - community header', () => {
|
||||
cy.intercept('GET', '/public/config', (req) => {
|
||||
req.reply((res) => {
|
||||
const conf = res.body;
|
||||
conf.exportHeaderAndFooter = 'For {{community.project.descriptor}} enjoyment';
|
||||
res.send(conf);
|
||||
});
|
||||
}).as('getPublicConfig');
|
||||
const projName = 'Export2'
|
||||
cy.createDefaultTinyProject('proj1', projName)
|
||||
|
||||
// visit client display
|
||||
cy.visitSkillsDisplay();
|
||||
cy.wait('@getPublicConfig')
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.wrapIframe().find('[data-cy="downloadTranscriptBtn"]').click()
|
||||
cy.readTranscriptForProj(projName).then((doc) => {
|
||||
expect(doc.numpages).to.equal(2)
|
||||
expect(clean(doc.text)).to.include('SkillTree Transcript')
|
||||
expect(clean(doc.text)).to.include(projName)
|
||||
expect(clean(doc.text)).to.include('Level: 0 / 5 ')
|
||||
expect(clean(doc.text)).to.include('Points: 0 / 100 ')
|
||||
expect(clean(doc.text)).to.include('Skills: 0 / 2 ')
|
||||
expect(clean(doc.text)).to.include(userName)
|
||||
expect(clean(doc.text)).to.not.include('Badges')
|
||||
|
||||
// should be a title on the 2nd page
|
||||
expect(clean(doc.text)).to.include('Subject: Subject 0'.toUpperCase())
|
||||
|
||||
expect(clean(doc.text)).to.include(expectedHeaderAndFooter)
|
||||
expect(clean(doc.text)).to.not.include(expectedHeaderAndFooterCommunityProtected)
|
||||
expect(clean(doc.text)).to.not.include('null')
|
||||
})
|
||||
});
|
||||
if (!Cypress.env('oauthMode')) {
|
||||
it('export user transcript - community protected header', () => {
|
||||
cy.intercept('GET', '/public/config', (req) => {
|
||||
req.reply((res) => {
|
||||
const conf = res.body;
|
||||
conf.exportHeaderAndFooter = 'For {{community.project.descriptor}} enjoyment';
|
||||
res.send(conf);
|
||||
});
|
||||
}).as('getPublicConfig');
|
||||
const projName = 'Export3'
|
||||
cy.createDefaultTinyProject('proj1', projName)
|
||||
|
||||
const allDragonsUser = '[email protected]'
|
||||
cy.fixture('vars.json').then((vars) => {
|
||||
cy.backendLogout();
|
||||
cy.backendRegister('user1', vars.defaultPass);
|
||||
cy.backendLogin(vars.rootUser, vars.defaultPass, true);
|
||||
cy.backendPost(`/root/users/user1/tags/dragons`, {tags: ['DivineDragon']});
|
||||
cy.backendPost(`/root/users/${vars.defaultUser}/tags/dragons`, {tags: ['DivineDragon']});
|
||||
cy.backendLogout();
|
||||
|
||||
cy.backendRegister(allDragonsUser, vars.defaultPass);
|
||||
cy.backendLogout();
|
||||
|
||||
cy.backendLogin(vars.defaultUser, vars.defaultPass);
|
||||
});
|
||||
cy.backendPost(`/admin/projects/proj1`, {
|
||||
projectId: 'proj1',
|
||||
name: projName,
|
||||
enableProtectedUserCommunity: true
|
||||
})
|
||||
|
||||
// visit client display
|
||||
cy.visitSkillsDisplay();
|
||||
cy.wait('@getPublicConfig')
|
||||
cy.clientDisplay().find(selectors.titleSection).contains('User Skills');
|
||||
cy.clientDisplay().find(selectors.myRankButton)
|
||||
|
||||
cy.wrapIframe().find('[data-cy="downloadTranscriptBtn"]').click()
|
||||
const pathToPdf = `cypress/downloads/${projName} - Firstname LastName (user1) - Transcript.pdf`
|
||||
|
||||
cy.readTranscript(pathToPdf).then((doc) => {
|
||||
expect(doc.numpages).to.equal(2)
|
||||
expect(clean(doc.text)).to.include('SkillTree Transcript')
|
||||
expect(clean(doc.text)).to.include(projName)
|
||||
expect(clean(doc.text)).to.include('Level: 0 / 5 ')
|
||||
expect(clean(doc.text)).to.include('Points: 0 / 100 ')
|
||||
expect(clean(doc.text)).to.include('Skills: 0 / 2 ')
|
||||
expect(clean(doc.text)).to.include(userName)
|
||||
expect(clean(doc.text)).to.not.include('Badges')
|
||||
|
||||
// should be a title on the 2nd page
|
||||
expect(clean(doc.text)).to.include('Subject: Subject 0'.toUpperCase())
|
||||
|
||||
expect(clean(doc.text)).to.not.include(expectedHeaderAndFooter)
|
||||
expect(clean(doc.text)).to.include(expectedHeaderAndFooterCommunityProtected)
|
||||
expect(clean(doc.text)).to.not.include('null')
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
const { readPdf } = require('./read-pdf')
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
@@ -39,4 +40,8 @@ module.exports = (on, config) => {
|
||||
}
|
||||
return launchOptions;
|
||||
});
|
||||
|
||||
on("task", {
|
||||
readPdf
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const pdf = require('pdf-parse')
|
||||
|
||||
const readPdf = (filename) => {
|
||||
const dataBuffer = fs.readFileSync(filename)
|
||||
return pdf(dataBuffer).then(function (data) {
|
||||
return {
|
||||
numpages: data.numpages,
|
||||
text: data.text,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { readPdf }
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,32 +38,96 @@
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
import selectors from './selectors.js'
|
||||
|
||||
const backend = 'http://localhost:8080';
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
Cypress.Commands.add("visitSkillsDisplay", (url = '') => {
|
||||
cy.visit(`${skillsDisplayHomePage}${url}`);
|
||||
})
|
||||
|
||||
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.request('POST', `${backend}/logout`);
|
||||
|
||||
if (grantRoot) {
|
||||
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 {
|
||||
cy.log(`User [${user}] already exists`)
|
||||
}
|
||||
});
|
||||
});
|
||||
Cypress.Commands.add("backendLogout", () => {
|
||||
return cy.request('POST', `${backend}/logout`);
|
||||
})
|
||||
|
||||
Cypress.Commands.add("backendPost", (url) => {
|
||||
return cy.request('POST', `${backend}/${url}`);
|
||||
})
|
||||
|
||||
Cypress.Commands.add("backendLogin", (user, pass) => {
|
||||
cy.request({
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: `${backend}/performLogin`,
|
||||
body: {
|
||||
@@ -75,12 +139,12 @@ Cypress.Commands.add("backendLogin", (user, pass) => {
|
||||
});
|
||||
|
||||
Cypress.Commands.add("backendLogout", () => {
|
||||
cy.request('POST', `${backend}/logout`);
|
||||
return cy.request('POST', `${backend}/logout`).as('request')
|
||||
});
|
||||
|
||||
|
||||
Cypress.Commands.add("backendPost", (url, body) => {
|
||||
cy.request('POST', `${backend}${url}`, body)
|
||||
return cy.request('POST', `${backend}${url}`, body)
|
||||
});
|
||||
|
||||
Cypress.Commands.add("reportSkill", (skillId, projId = 'proj1') => {
|
||||
@@ -107,11 +171,11 @@ Cypress.Commands.add("backendAddSkill", (skillId, version = 0, projId = 'proj1',
|
||||
})
|
||||
});
|
||||
|
||||
Cypress.Commands.add("createDefaultProject", (numSubj = 3, numSkillsPerSubj = 2, pointIncrement = 50, numPerformToCompletion = 1, projId = 'proj1') => {
|
||||
Cypress.Commands.add("createDefaultProject", (numSubj = 3, numSkillsPerSubj = 2, pointIncrement = 50, numPerformToCompletion = 1, projId = 'proj1', projName = null) => {
|
||||
const skillIdsToCreate = ['IronMan', 'Thor']
|
||||
cy.backendPost(`/app/projects/${projId}`, {
|
||||
projectId: projId,
|
||||
name: `Very Cool Project with id ${projId}`
|
||||
name: projName || `Very Cool Project with id ${projId}`
|
||||
}).then((resp => {
|
||||
if (Cypress.env('oauthMode')) {
|
||||
// if we're in oauthMode, then the foo-hyrda user will own the project. so we need to give the
|
||||
@@ -140,8 +204,8 @@ Cypress.Commands.add("createDefaultProject", (numSubj = 3, numSkillsPerSubj = 2,
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add("createDefaultTinyProject", (projId = 'proj1') => {
|
||||
cy.createDefaultProject(1, 2, 50, 1, projId)
|
||||
Cypress.Commands.add("createDefaultTinyProject", (projId = 'proj1', projName = null) => {
|
||||
cy.createDefaultProject(1, 2, 50, 1, projId, projName)
|
||||
});
|
||||
|
||||
Cypress.Commands.add("iframe", (handleIframeBody) => {
|
||||
@@ -167,20 +231,18 @@ Cypress.Commands.add('visitHomePage', (homepage) => {
|
||||
cy.skillsLog(`Visit Home page for test [${Cypress.mocha.getRunner().test.title}]`)
|
||||
});
|
||||
|
||||
Cypress.Commands.add("cdClickSubj", (subjIndex, expectedTitle) => {
|
||||
cy.wrapIframe().find(`.user-skill-subject-tile:nth-child(${subjIndex + 1})`).first().click();
|
||||
if (expectedTitle) {
|
||||
cy.wrapIframe().contains(expectedTitle);
|
||||
}
|
||||
Cypress.Commands.add("cdClickSubj", () => {
|
||||
cy.wrapIframe().find(selectors.firstSubjectTileBtn).click();
|
||||
cy.wrapIframe().find(selectors.titleSection).contains('Subject 0');
|
||||
});
|
||||
|
||||
Cypress.Commands.add("cdBack", (expectedTitle = 'User Skills') => {
|
||||
cy.wrapIframe().find('[data-cy=back]').click()
|
||||
cy.wrapIframe().find(selectors.backButton).click()
|
||||
cy.wrapIframe().contains(expectedTitle);
|
||||
|
||||
// back button should not exist on the home page, whose title is the default value
|
||||
if (expectedTitle === 'User Skills') {
|
||||
cy.wrapIframe().find('[data-cy=back]').should('not.exist');
|
||||
cy.wrapIframe().find(selectors.backButton).should('not.exist');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -211,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, },
|
||||
@@ -234,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,
|
||||
@@ -249,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;
|
||||
}))
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,20 +33,26 @@ import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
before(() => {
|
||||
cy.readFile('../skills-int-client-js/package.json').then((packageJson) => {
|
||||
const skillsClientJsVersion = packageJson['dependencies']['@skilltree/skills-client-js']
|
||||
Cypress.env('skills-client-js-version', skillsClientJsVersion)
|
||||
cy.log(`Running with skills-client-js version [${skillsClientJsVersion}]`)
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// first call to npm fails, looks like this may be the bug: https://github.com/cypress-io/cypress/issues/6081
|
||||
cy.exec('npm version', { failOnNonZeroExit: false })
|
||||
cy.exec('npm run backend:resetDb')
|
||||
|
||||
cy.log(`Running with skills-client-js version [${Cypress.env('skills-client-js-version')}]`)
|
||||
|
||||
cy.fixture('vars.json').then((vars) => {
|
||||
cy.request('api/config').then(resp => {
|
||||
cy.backendRegister(vars.rootUser, vars.defaultPass, true);
|
||||
cy.backendRegister(vars.defaultUser, vars.defaultPass); // used by the skills-int-service app
|
||||
const oauthMode = resp.body.authenticator === 'hydra' || resp.body.authenticator === 'http://localhost:8080/oauth2/authorization/hydra'
|
||||
Cypress.env('oauthMode', oauthMode)
|
||||
if (!oauthMode) {
|
||||
if (!Cypress.env('oauthMode')) {
|
||||
cy.backendLogin(vars.defaultUser, vars.defaultPass);
|
||||
Cypress.env('proxyUser', 'user1')
|
||||
Cypress.env('tokenUrl', '/api/users/user1/token')
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 Utils from "../e2e/Utils";
|
||||
|
||||
const buildFirstSubjectTileBtnSelector = () => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')) {
|
||||
return '[data-cy="subjectTile-subj0"] [data-cy="subjectTileBtn"]'
|
||||
}
|
||||
|
||||
return '[data-cy="subjectTile"]:nth-child(1)'
|
||||
}
|
||||
|
||||
const buildMyRankBtnSelector = () => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')) {
|
||||
return '[data-cy="myRankBtn"]'
|
||||
}
|
||||
|
||||
return '[data-cy="myRank"]'
|
||||
}
|
||||
|
||||
|
||||
const buildSkillLinkSelector = (skillId) => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')) {
|
||||
return `[data-cy="skillProgressTitle-${skillId}"] [data-cy="skillProgressTitle"]`
|
||||
}
|
||||
|
||||
return `#skillRow-${skillId} [data-cy="skillProgressTitle"]`
|
||||
}
|
||||
|
||||
const buildSkillTitleSelector = (skillId) => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')) {
|
||||
return `[data-cy="skillProgressTitle-${skillId}"]`
|
||||
}
|
||||
|
||||
return `#skillProgressTitle-${skillId}`
|
||||
}
|
||||
|
||||
const buildBreadcrumbLinkSelector = (itemId) => {
|
||||
if (Utils.skillsServiceVersionLaterThan('3.0.0')) {
|
||||
return `[data-cy="breadcrumbLink-${itemId}"]`
|
||||
}
|
||||
return`[data-cy="breadcrumb-${itemId}"]`
|
||||
}
|
||||
|
||||
|
||||
const selectors = {
|
||||
backButton: '[data-cy=back]',
|
||||
myRankButton: buildMyRankBtnSelector(),
|
||||
titleSection: '[data-cy="skillsTitle"]',
|
||||
nextSkillButton: '[data-cy="nextSkill"]',
|
||||
firstSubjectTileBtn: buildFirstSubjectTileBtnSelector(),
|
||||
buildSkillLinkSelector: buildSkillLinkSelector,
|
||||
buildSkillTitleSelector: buildSkillTitleSelector,
|
||||
buildBreadcrumbLinkSelector: buildBreadcrumbLinkSelector
|
||||
}
|
||||
export default selectors
|
||||
@@ -1,4 +1,4 @@
|
||||
-- Copyright 2020 SkillTree
|
||||
-- Copyright 2025 SkillTree
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-- you may not use this file except in compliance with the License.
|
||||
@@ -12,7 +12,20 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
-- creating Inception project is expensive so lets not delete it
|
||||
delete from PROJECT_DEFINITION where PROJECT_ID <> 'Inception';
|
||||
delete from USER_ATTRS where USER_ID <> '[email protected]' and USER_ID <> '[email protected]';
|
||||
delete from GLOBAL_BADGE_LEVEL_DEFINITION;
|
||||
delete from SKILL_DEFINITION where PROJECT_ID is null;
|
||||
delete from project_definition where project_id <> 'Inception';
|
||||
delete from quiz_definition;
|
||||
delete from user_attrs where user_id <> '[email protected]' and user_id <> '[email protected]';
|
||||
delete from user_roles where user_id = '[email protected]' and (role_name = 'ROLE_SUPER_DUPER_USER' OR role_name = 'ROLE_SUPERVISOR' OR role_name = 'ROLE_PROJECT_ADMIN' OR role_name = 'ROLE_PRIVATE_PROJECT_USER' OR role_name = 'ROLE_DASHBOARD_ADMIN_ACCESS');
|
||||
delete from user_roles where user_id = '[email protected]' and role_name = 'ROLE_SUPERVISOR';
|
||||
delete from global_badge_level_definition;
|
||||
delete from skill_definition where project_id is null;
|
||||
delete from user_achievement;
|
||||
delete from settings where setting_group <> 'project.inception';
|
||||
delete from user_points where project_id = 'Inception';
|
||||
delete from user_achievement where project_id = 'Inception';
|
||||
delete from user_performed_skill where project_id = 'Inception';
|
||||
delete from user_events where project_id = 'Inception';
|
||||
delete from user_tags;
|
||||
delete from notifications;
|
||||
delete from user_actions_history;
|
||||
delete from custom_icons;
|
||||
@@ -12,11 +12,11 @@
|
||||
"licenseCheck": "npm run licenseHeaderCheck && npm run licenseDepCheck",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"cy:open": "cypress open",
|
||||
"cy:open:oauth": "cypress open --env oauthMode=true",
|
||||
"cy:run": "npm run licenseCheck && cypress run",
|
||||
"cy:run:oauth": "npm run licenseCheck && cypress run --env authMode=oauth",
|
||||
"cy:run:oauth": "npm run licenseCheck && cypress run --env oauthMode=true",
|
||||
"cy:run:timeout": "npm run licenseCheck && cypress run --env timeout=true --spec \"cypress/e2e/timeout.specs.js\"",
|
||||
"cy:open:dev:js": "cypress open --config baseUrl=http://localhost:8092",
|
||||
"cy:open:dev:ng": "cypress open --config baseUrl=http://localhost:4200",
|
||||
"cy:open:dev": "cypress open --config baseUrl=http://localhost:8092",
|
||||
"integration:start": "java -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ../skills-int-service/target/skills-int-service-*.jar",
|
||||
"integration:start:oauth": "java -Dskills.service.authenticator=hydra -Dspring.config.location=classpath:/application.properties,file:serverConfigs/integration_application.properties -jar ../skills-int-service/target/skills-int-service-*.jar",
|
||||
"integration:start:background": "mkdir -p ./target/logs && npm run integration:start > ./target/logs/integration-apps.out &",
|
||||
@@ -48,12 +48,12 @@
|
||||
"dev:removeNpmLinks": "mvn --batch-mode clean package && java -cp target/skills-int-e2e-test-*.jar -Dloader.main=skills.RemoveNpmLinks org.springframework.boot.loader.launch.PropertiesLauncher"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "13.6.3",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"cypress": "14.5.2",
|
||||
"license-check-and-add": "4.0.5",
|
||||
"license-checker": "25.0.1",
|
||||
"semver": "7.3.2",
|
||||
"wait-on": "5.2.0",
|
||||
"wait-port": "0.2.9"
|
||||
},
|
||||
"dependencies": {}
|
||||
"pdf-parse": "1.1.1",
|
||||
"semver": "7.8.5",
|
||||
"wait-on": "9.1.0",
|
||||
"wait-port": "1.1.0"
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.5.0-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>
|
||||
|
||||
<properties>
|
||||
<groovy.version>4.0.7</groovy.version>
|
||||
<!-- <groovy.version>4.0.7</groovy.version>-->
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zeroturnaround</groupId>
|
||||
@@ -104,7 +104,6 @@
|
||||
<!-- mvn license:add-third-party-->
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-cli</id>
|
||||
@@ -133,6 +132,7 @@
|
||||
<includedLicense>Common Development and Distribution License 1.1 (CDDL-1.1) + The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
|
||||
<includedLicense>Eclipse Public License - Version 2.0</includedLicense>
|
||||
<includedLicense>The GNU General Public License (GPL) Version 2 with the Classpath Exception</includedLicense>
|
||||
<includedLicense>GNU Lesser General Public License</includedLicense>
|
||||
</includedLicenses>
|
||||
<licenseMerges>
|
||||
<licenseMerge>The Apache Software License, Version 2.0|Apache License, Version 2.0</licenseMerge>
|
||||
@@ -144,7 +144,9 @@
|
||||
<licenseMerge>The 3-Clause BSD License|New BSD License</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0</licenseMerge>
|
||||
<licenseMerge>MIT License|The MIT License</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0</licenseMerge>
|
||||
<licenseMerge>MIT License|MIT</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0|EPL-2.0</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|Eclipse Public License - v 2.0</licenseMerge>
|
||||
<licenseMerge>The GNU General Public License (GPL) Version 2 with the Classpath Exception|GPL2 w/ CPE</licenseMerge>
|
||||
</licenseMerges>
|
||||
</configuration>
|
||||
|
||||
+12
-2
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,15 @@
|
||||
spring.datasource.username = postgres
|
||||
spring.datasource.url = jdbc:postgresql://localhost:5432/skills
|
||||
spring.datasource.password = skillsPassword
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
|
||||
|
||||
skills.config.ui.userCommunityUserTagKey=dragons
|
||||
skills.config.ui.userCommunityUserTagValue=DivineDragon
|
||||
skills.config.ui.userCommunityParagraphValidationRegex=^(?i)(?s)((?!divinedragon).)*$
|
||||
skills.config.ui.userCommunityParagraphValidationMessage=May not contain divinedragon word
|
||||
skills.config.ui.userCommunityRestrictedDescriptor=Divine Dragon
|
||||
skills.config.ui.userCommunityBeforeLabel=For
|
||||
skills.config.ui.userCommunityAfterLabel=Nation
|
||||
skills.config.ui.defaultCommunityDescriptor=All Dragons
|
||||
|
||||
logging.level.org.springframework.security.web.access.intercept.FilterSecurityInterceptor=DEBUG
|
||||
logging.level.org.springframework.security.web.access.expression=DEBUG
|
||||
@@ -29,3 +37,5 @@ logging.level.skills=DEBUG
|
||||
|
||||
skills.config.client.loggingEnabled=true
|
||||
skills.config.client.loggingLevel=TRACE
|
||||
|
||||
skills.config.client.loggingMaxRequestsPerMinute=5000
|
||||
+13
-2
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,4 +17,15 @@ spring.security.oauth2.client.registration.hydra.client-secret: client-secret
|
||||
spring.security.oauth2.client.registration.hydra.scope: openid
|
||||
spring.security.oauth2.client.registration.hydra.clientName: Hydra
|
||||
spring.security.oauth2.client.registration.hydra.iconClass: fas fa-key
|
||||
spring.security.oauth2.client.provider.hydra.issuer-uri: http://localhost:4444/
|
||||
spring.security.oauth2.client.provider.hydra.issuer-uri: http://localhost:4444/
|
||||
|
||||
skills.config.ui.userCommunityUserTagKey=dragons
|
||||
skills.config.ui.userCommunityUserTagValue=DivineDragon
|
||||
skills.config.ui.userCommunityParagraphValidationRegex=^(?i)(?s)((?!divinedragon).)*$
|
||||
skills.config.ui.userCommunityParagraphValidationMessage=May not contain divinedragon word
|
||||
skills.config.ui.userCommunityRestrictedDescriptor=Divine Dragon
|
||||
skills.config.ui.userCommunityBeforeLabel=For
|
||||
skills.config.ui.userCommunityAfterLabel=Nation
|
||||
skills.config.ui.defaultCommunityDescriptor=All Dragons
|
||||
|
||||
skills.config.client.loggingMaxRequestsPerMinute=5000
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -103,12 +103,5 @@ class ReleaseClientLibs {
|
||||
json.dependencies.findAll { it.key.startsWith(Vars.NpmNamespace) }.each {
|
||||
assert json.dependencies."${it.key}" == newVersion
|
||||
}
|
||||
|
||||
if (npmProj.angularModule) {
|
||||
File loc = new File(npmProj.loc, "projects/skilltree/skills-client-ng/")
|
||||
assert loc.exists()
|
||||
NpmProj angularProj = new NpmProj(loc: loc, name: loc.name, doOthersLinkToMe: true)
|
||||
updateVersion(angularProj, newVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
|
||||
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
|
||||
|
||||
https://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.
|
||||
*/
|
||||
package skills
|
||||
|
||||
//import callStack.profiler.CProf
|
||||
//import callStack.profiler.Profile
|
||||
import groovy.util.logging.Slf4j
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
import javax.print.attribute.SetOfIntegerSyntax
|
||||
import java.nio.file.Files
|
||||
|
||||
@Slf4j
|
||||
class SetupNpmLinks {
|
||||
|
||||
static void main(String[] args) {
|
||||
boolean shouldPrune = args.find({ it.equalsIgnoreCase("--prune") })
|
||||
new SetupNpmLinks(shouldPrune: shouldPrune).init().doLink()
|
||||
// log.info("Execution Prof:\n{}", CProf.prettyPrint())
|
||||
}
|
||||
|
||||
// configure
|
||||
File root = new File("./")
|
||||
boolean shouldPrune = false
|
||||
|
||||
// private
|
||||
TitlePrinter titlePrinter = new TitlePrinter()
|
||||
List<NpmProj> projs
|
||||
SetupNpmLinks init(){
|
||||
projs = new NpmProjBuilder(loc: root).build()
|
||||
return this
|
||||
}
|
||||
|
||||
// @Profile
|
||||
void doLink() {
|
||||
log.info("Should Prune = [{}]", shouldPrune)
|
||||
assert projs
|
||||
new RemoveNpmLinks(shouldPrune: shouldPrune, npmInstall: false).init().removeAnyExistingLinks()
|
||||
createLinks()
|
||||
npmLinkToSkills()
|
||||
install()
|
||||
validateLinks()
|
||||
build()
|
||||
}
|
||||
|
||||
// @Profile
|
||||
private void install() {
|
||||
titlePrinter.printTitle("install")
|
||||
projs.each { NpmProj npmProj ->
|
||||
npmProj.exec("npm install --force")
|
||||
}
|
||||
}
|
||||
|
||||
// @Profile
|
||||
private void build() {
|
||||
titlePrinter.printTitle("build")
|
||||
projs.each { NpmProj npmProj ->
|
||||
npmProj.exec("npm run build")
|
||||
}
|
||||
}
|
||||
|
||||
// @Profile
|
||||
private void validateLinks() {
|
||||
titlePrinter.printTitle("validate links")
|
||||
projs.findAll({ it.hasLinksToOtherProjects }).each { NpmProj npmProj ->
|
||||
titlePrinter.printSubTitle("validating [${npmProj.getSkillsModulesDir().absolutePath}]")
|
||||
npmProj.exec("ls -l node_modules/@skilltree/")
|
||||
npmProj.skillsDependenciesFromPackageJson.each {
|
||||
File shouldBeSymbolic = new File(npmProj.loc, "node_modules/${it}")
|
||||
assert shouldBeSymbolic.exists()
|
||||
assert Files.isSymbolicLink(shouldBeSymbolic.toPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Profile
|
||||
private void npmLinkToSkills() {
|
||||
titlePrinter.printTitle("link")
|
||||
projs.findAll({ it.hasLinksToOtherProjects }).each { NpmProj npmProj ->
|
||||
npmProj.skillsDependenciesFromPackageJson.each {
|
||||
titlePrinter.printSubTitle("Linking module [${it}] in project [${npmProj}]")
|
||||
|
||||
npmProj.exec("npm link --force ${it}".toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Profile
|
||||
private void createLinks() {
|
||||
titlePrinter.printTitle("create links")
|
||||
projs.findAll({ it.doOthersLinkToMe }).each {
|
||||
titlePrinter.printSubTitle("create link for ${it.loc.name}")
|
||||
it.exec("ls")
|
||||
it.exec("node -v")
|
||||
it.exec("npm link")
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2025 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>skills</groupId>
|
||||
<artifactId>skills-client-integration</artifactId>
|
||||
<version>3.5.0-SNAPSHOT</version>
|
||||
<version>3.6.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>skills-int-service</artifactId>
|
||||
<description>SkillTree Client Lib Integration Tests</description>
|
||||
@@ -23,6 +23,10 @@
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-restclient</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -57,7 +61,6 @@
|
||||
<!-- mvn license:add-third-party-->
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-cli</id>
|
||||
@@ -99,8 +102,10 @@
|
||||
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License 1.0</licenseMerge>
|
||||
<licenseMerge>The 3-Clause BSD License|New BSD License</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|EPL 2.0|EPL-2.0</licenseMerge>
|
||||
<licenseMerge>Eclipse Public License - Version 2.0|Eclipse Public License - v 2.0</licenseMerge>
|
||||
<licenseMerge>The GNU General Public License (GPL) Version 2 with the Classpath Exception|GPL2 w/ CPE</licenseMerge>
|
||||
<licenseMerge>MIT License|MIT</licenseMerge>
|
||||
</licenseMerges>
|
||||
</configuration>
|
||||
</execution>
|
||||
@@ -112,7 +117,6 @@
|
||||
mvn com.mycila:license-maven-plugin:3.0:format-->
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<header>${basedir}/../license-add/LICENSE-HEADER.txt</header>
|
||||
<excludes>
|
||||
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,10 +15,8 @@
|
||||
*/
|
||||
package skills.example;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -27,6 +25,8 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import skills.example.SkillsExampleApplication.SkillsConfig;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -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<>();
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+18
-7
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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;
|
||||
@@ -23,11 +25,9 @@ import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
|
||||
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
|
||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.hc.core5.http.config.RegistryBuilder;
|
||||
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
||||
import org.apache.hc.core5.ssl.SSLContexts;
|
||||
import org.apache.hc.core5.ssl.TrustStrategy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.client.RestTemplateCustomizer;
|
||||
import org.springframework.boot.restclient.RestTemplateCustomizer;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -47,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();
|
||||
@@ -69,6 +79,7 @@ public class SecurityRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
|
||||
return HttpClients.custom()
|
||||
.useSystemProperties()
|
||||
.setDefaultCookieStore(cookieStore)
|
||||
.setConnectionManager(poolingHttpClientConnectionManager)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,15 +18,18 @@ package skills.example.filters;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@Order(SecurityProperties.DEFAULT_FILTER_ORDER-1)
|
||||
@Order(VueEntryPointFilter.BEFORE_SECURITY_FILTER_ORDER)
|
||||
public class VueEntryPointFilter implements Filter {
|
||||
|
||||
// -100 (Default Security Order) - 1
|
||||
public static final int BEFORE_SECURITY_FILTER_ORDER = -101;
|
||||
|
||||
@Autowired
|
||||
private VueEntryPointFilterUtils vueEntryPointFilterUtils;
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2020 SkillTree
|
||||
/*
|
||||
* Copyright 2025 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2020 SkillTree
|
||||
# Copyright 2025 SkillTree
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": "airbnb-base",
|
||||
"rules": {
|
||||
"no-underscore-dangle": 0,
|
||||
"max-len": ["error", {'code': 200}],
|
||||
},
|
||||
"globals": {
|
||||
"XMLHttpRequest": 'readonly',
|
||||
"document": 'readonly',
|
||||
"alert": 'readonly',
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jasmine": true,
|
||||
"jest": true,
|
||||
}
|
||||
};
|
||||
+32
-15
@@ -1,5 +1,5 @@
|
||||
<!---
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2026 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,25 +17,42 @@ limitations under the License.
|
||||
# SkillTree Client Lib - Native JS Lib
|
||||
|
||||
<p>
|
||||
<a href="https://www.npmjs.com/package/@skilltree/skills-client-js">
|
||||
<img src="https://flat.badgen.net/npm/v/@skilltree/skills-client-js" alt="Current version">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@skilltree/skills-client-js">
|
||||
<img src="https://flat.badgen.net/npm/dt/@skilltree/skills-client-js" alt="npm downloads">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@skilltree/skills-client-js">
|
||||
<img src="https://flat.badgen.net/npm/dw/@skilltree/skills-client-js" alt="npm weekly downloads">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@skilltree/skills-client-js">
|
||||
<img src="https://img.shields.io/npm/l/@skilltree/skills-client-js.svg?sanitize=true" alt="License">
|
||||
</a>
|
||||
<img src="https://img.shields.io/npm/d18m/%40skilltree%2Fskills-client-js" alt="npm downloads">
|
||||
<img src="https://img.shields.io/npm/dw/%40skilltree%2Fskills-client-js" alt="npm weekly downloads">
|
||||
<img src="https://img.shields.io/npm/l/%40skilltree%2Fskills-client-js" alt="License">
|
||||
<img src="https://img.shields.io/npm/v/%40skilltree%2Fskills-client-js" alt="Latest Version">
|
||||
<img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/%40skilltree%2Fskills-client-js">
|
||||
</p>
|
||||
|
||||
SkillTree is an innovative approach to implementing application training. SkillTree's ``skills-client-js`` project facilitates rapid integration with the SkillTree service.
|
||||
|
||||
To learn about the SkillTree platform please visit our [Official Documentation](https://code.nsa.gov/skills-docs/).
|
||||
To learn about the SkillTree platform please visit our [Official Documentation](https://skilltreeplatform.dev/).
|
||||
These pages provide in-depth guidance on the installation, usage and contribution.
|
||||
|
||||
## Library Features
|
||||
|
||||
SkillTree client library offers a comprehensive set of features:
|
||||
|
||||
- **Seamless navigation and visualization:** Our Skill Display component provides a fully navigable Ranking and Visualization view, making it easy for users to explore and understand their skills.
|
||||
- **Effortless skill event reporting:** The SkillsReporter JS utility simplifies the process of reporting skill events, ensuring accurate and timely tracking of user progress.
|
||||
- **Convenient level display:** Our level display component provides a straightforward way to showcase a user's current overall level within the application.
|
||||
- **Global event handling:** Our JS functions enable applications to register to receive skill events, allowing for real-time notifications and updates. For example, you can use this feature to display encouraging messages when users earn points or complete levels, further enhancing their engagement and motivation.
|
||||
|
||||
## Important Note
|
||||
|
||||
To enable seamless integration with Skills Display, this library utilizes an iFrame wrapper to retrieve
|
||||
the necessary views and data from the skills-service application. This deliberate architectural design choice is a key
|
||||
aspect of the SkillTree Platform, as it minimizes the need for frequent upgrades to the skills-client library.
|
||||
|
||||
By decoupling the Skills Display functionality from the skills-client library, we've achieved a significant advantage:
|
||||
most updates and improvements to Skills Display will be automatically reflected in the skills-client library whenever
|
||||
the skills-service application is upgraded. This means that skills-client integrators can enjoy the benefits of new
|
||||
features and enhancements without the need for manual library updates.
|
||||
|
||||
As a result, **the skills-client libraries are intentionally designed to be relatively static, with infrequent releases**.
|
||||
This approach ensures that integrators can rely on a stable and consistent library, while still benefiting from the
|
||||
ongoing evolution of the SkillTree Platform.
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
To learn how to install and use this library please visit [SkillTree's Native JS Integration Guide](https://code.nsa.gov/skills-docs/skills-client/js.html).
|
||||
To learn how to install and use this library please visit [SkillTree's Native JS Integration Guide](https://skilltreeplatform.dev/skills-client/js.html).
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env'],
|
||||
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
|
||||
plugins: ['@babel/plugin-transform-runtime'],
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2026 SkillTree
|
||||
|
||||
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
|
||||
|
||||
https://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 globals from "globals";
|
||||
import { defineConfig } from "eslint/config";
|
||||
import js from "@eslint/js";
|
||||
|
||||
export default defineConfig([
|
||||
{ files: ["**/*.js"], languageOptions: { globals: globals.browser } },
|
||||
{ files: ["**/*.js"], plugins: { js }, extends: ["js/recommended"] },
|
||||
]);
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2020 SkillTree
|
||||
Copyright 2026 SkillTree
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@skilltree/skills-client-js",
|
||||
"version": "3.5.0",
|
||||
"version": "3.6.5",
|
||||
"license": "Apache-2.0",
|
||||
"description": "SkillTree Client Lib - Native JS Lib",
|
||||
"main": "dist/skills-client-js.umd.min.js",
|
||||
@@ -17,43 +17,46 @@
|
||||
"licenseHeaderCheck": " npx license-check-and-add check -f license-add/license-add-config.json",
|
||||
"licenseHeaderAdd": " npx license-check-and-add add -f license-add/license-add-config.json",
|
||||
"licenseCheck": "npm run licenseHeaderCheck && npm run licenseDepCheck ",
|
||||
"build": "npm run licenseCheck && npm run test && 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",
|
||||
"build:esm": "rollup --config --format esm --file dist/skills-client-js.esm.min.js",
|
||||
"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",
|
||||
"build": "npm run lint && npm run licenseCheck && npm run test && npm run clean && npm run build:umd && npm run build:esm",
|
||||
"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 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",
|
||||
"lint": "eslint --ext .js src/**"
|
||||
"lint": "eslint src/**"
|
||||
},
|
||||
"author": "SkillTree Team",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.11.6",
|
||||
"@babel/core": "7.11.6",
|
||||
"@babel/plugin-transform-runtime": "7.13.10",
|
||||
"@babel/polyfill": "7.11.5",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"@rollup/plugin-commonjs": "15.1.0",
|
||||
"@rollup/plugin-node-resolve": "9.0.0",
|
||||
"@rollup/plugin-replace": "2.3.3",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"babel-jest": "26.5.2",
|
||||
"eslint": "7.11.0",
|
||||
"eslint-config-airbnb-base": "14.2.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"jest": "26.5.3",
|
||||
"license-check-and-add": "3.0.4",
|
||||
"@babel/cli": "7.28.6",
|
||||
"@babel/core": "7.29.6",
|
||||
"@babel/plugin-transform-runtime": "7.29.0",
|
||||
"@babel/polyfill": "7.12.1",
|
||||
"@babel/preset-env": "7.29.5",
|
||||
"@babel/runtime": "7.29.2",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@rollup/plugin-babel": "7.1.0",
|
||||
"@rollup/plugin-commonjs": "29.0.3",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-node-resolve": "16.0.3",
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/plugin-terser": "1.0.0",
|
||||
"axios-mock-adapter": "2.1.0",
|
||||
"babel-jest": "30.5.1",
|
||||
"eslint": "10.10.0",
|
||||
"globals": "17.12.0",
|
||||
"jest": "30.5.1",
|
||||
"jest-environment-jsdom": "30.5.1",
|
||||
"license-check-and-add": "4.0.5",
|
||||
"license-checker": "25.0.1",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"rollup": "2.30.0",
|
||||
"rollup-plugin-babel": "4.4.0",
|
||||
"rollup-plugin-eslint": "7.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"regenerator-runtime": "0.14.1",
|
||||
"rollup": "4.63.3",
|
||||
"xhr-mock": "2.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": "6.1.2",
|
||||
"axios": "1.7.4",
|
||||
"@stomp/stompjs": "7.3.0",
|
||||
"axios": "1.20.0",
|
||||
"js-logger": "1.6.1",
|
||||
"postmate": "1.5.2",
|
||||
"sockjs-client": "1.6.1"
|
||||
@@ -65,7 +68,7 @@
|
||||
"User Engagement",
|
||||
"Rapid Integration"
|
||||
],
|
||||
"homepage": "https://code.nsa.gov/skills-docs/",
|
||||
"homepage": "https://skilltreeplatform.dev/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/NationalSecurityAgency/skills-client.git"
|
||||
@@ -73,4 +76,4 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/NationalSecurityAgency/skills-client/issues"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2026 SkillTree
|
||||
|
||||
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
|
||||
|
||||
https://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 terser from '@rollup/plugin-terser';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
// import pkg from './package.json';
|
||||
|
||||
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
||||
console.log(`running version ${pkg.version}`);
|
||||
|
||||
module.exports = {
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
// need to pass in format, file (and name if format == umd) via cmd line
|
||||
sourcemap: true,
|
||||
},
|
||||
preserveSymlinks: true,
|
||||
plugins: [
|
||||
babel({
|
||||
babelHelpers: 'runtime',
|
||||
exclude: 'node_modules/**',
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
__skillsClientVersion__: `${pkg.name}-${pkg.version}`,
|
||||
preventAssignment: true,
|
||||
}),
|
||||
commonjs(),
|
||||
terser(), // comment out to remove minimization
|
||||
],
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { eslint } from 'rollup-plugin-eslint';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
import pkg from './package.json';
|
||||
|
||||
module.exports = {
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
// need to pass in format, file (and name if format == umd) via cmd line
|
||||
sourcemap: true,
|
||||
},
|
||||
preserveSymlinks: true,
|
||||
plugins: [
|
||||
eslint(),
|
||||
babel({
|
||||
runtimeHelpers: true,
|
||||
exclude: 'node_modules/**',
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
__skillsClientVersion__: `${pkg.name}-${pkg.version}`,
|
||||
}),
|
||||
commonjs(),
|
||||
terser(), // comment out to remove minimization
|
||||
],
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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()}`);
|
||||
}
|
||||
@@ -99,6 +99,10 @@ export default {
|
||||
return typeof authenticator === 'string' && authenticator.startsWith(`${serviceUrl}/oauth2/authorization`);
|
||||
},
|
||||
|
||||
assignWindowLocation(newLocation) {
|
||||
window.location.assign(newLocation);
|
||||
},
|
||||
|
||||
getAuthenticationToken(authenticator, serviceUrl, projectId, oauthRedirect = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isOAuthMode = this.isOAuthMode(authenticator, serviceUrl);
|
||||
@@ -107,7 +111,6 @@ export default {
|
||||
xhr.open('GET', authenticator);
|
||||
} else {
|
||||
xhr.open('GET', `${serviceUrl}/api/projects/${projectId}/token`);
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
@@ -115,9 +118,9 @@ export default {
|
||||
if (xhr.status !== 200) {
|
||||
if (isOAuthMode && oauthRedirect && xhr.status === 401) {
|
||||
// if we get 401 and we are using OAuth, then redirect to the OAuth Provider
|
||||
const oauthAuthenticator = `${authenticator}?skillsRedirectUri=${window.location}`;
|
||||
const oauthAuthenticator = `${authenticator}?skillsRedirectUri=${encodeURIComponent(window.location.href)}`;
|
||||
log.info(`SkillsClient::SkillService::unable to get oAuth token, navigating to [${oauthAuthenticator}]`);
|
||||
window.location.assign(oauthAuthenticator);
|
||||
this.assignWindowLocation(oauthAuthenticator);
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`SkillTree: Unable to authenticate using [${authenticator}] endpoint. Response Code=[${xhr.status}].\n
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,11 +66,24 @@ const exportObject = {
|
||||
authenticator,
|
||||
authToken,
|
||||
oauthRedirect = false,
|
||||
enabled = true,
|
||||
}) {
|
||||
if (this.isInitialized()) {
|
||||
log.warn('SkillsConfiguration already initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug(`SkillsConfiguration::configure params serviceUrl=[${serviceUrl}], projectId=[${projectId}], `
|
||||
+ `authenticator=[${authenticator}], authToken=[${authToken}], oauthRedirect=${oauthRedirect}, enabled=${enabled}`)
|
||||
|
||||
this.enabled = enabled
|
||||
if (!enabled) {
|
||||
log.useDefaults();
|
||||
log.setLevel(log.TRACE)
|
||||
log.info('SkillsConfiguration is disabled.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!this.skillsClientVersion) {
|
||||
// this will be replaced at build time with the current skills-client-js
|
||||
// version. extensions of the plain JS client (vue, react, etc), should
|
||||
@@ -91,7 +104,7 @@ const exportObject = {
|
||||
this.authenticator = authenticator;
|
||||
this.authToken = authToken;
|
||||
|
||||
skillsService.getServiceStatus(`${this.getServiceUrl()}/public/status`).then((response) => {
|
||||
return skillsService.getServiceStatus(`${this.getServiceUrl()}/public/status`).then((response) => {
|
||||
this.status = response.status;
|
||||
skillsService.configureLogging(this.getServiceUrl(), response);
|
||||
log.info(`Returned status [${JSON.stringify(response)}]`);
|
||||
@@ -101,7 +114,7 @@ const exportObject = {
|
||||
}
|
||||
|
||||
if (!this.isPKIMode() && !this.getAuthToken()) {
|
||||
skillsService.getAuthenticationToken(this.getAuthenticator(), this.getServiceUrl(), this.getProjectId(), oauthRedirect)
|
||||
return skillsService.getAuthenticationToken(this.getAuthenticator(), this.getServiceUrl(), this.getProjectId(), oauthRedirect)
|
||||
.then((token) => {
|
||||
this.setAuthToken(token);
|
||||
setInitialized(this);
|
||||
@@ -112,7 +125,6 @@ const exportObject = {
|
||||
setConfigureWasCalled();
|
||||
}
|
||||
}).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error getting service status', error);
|
||||
setConfigureWasCalled();
|
||||
initializedResolvers.resolve();
|
||||
@@ -152,6 +164,10 @@ SkillsConfiguration is a singleton and you only have to do this once. Please see
|
||||
return this.projectId;
|
||||
},
|
||||
|
||||
isDisabled() {
|
||||
return !this.enabled;
|
||||
},
|
||||
|
||||
getServiceUrl() {
|
||||
return this.serviceUrl;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,17 +26,15 @@ const skillsClientDisplayPath = 'skillsClientDisplayPath';
|
||||
const markedForDestruction = [];
|
||||
|
||||
export default class SkillsDisplayJS {
|
||||
/* eslint-disable object-curly-newline */
|
||||
constructor({ options, theme, version, handleRouteChanged, userId } = {}) {
|
||||
log.debug(`SkillsClient::SkillsDisplayJS::Constructing with options [${options}], theme [${theme}], version [${version}], userId [${userId}]`);
|
||||
this._options = { ...{ }, ...options };
|
||||
this._options = { parentPath: window.location.pathname, ...options };
|
||||
this._theme = theme;
|
||||
this._version = version;
|
||||
this._handleRouteChanged = handleRouteChanged;
|
||||
this._userId = userId;
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
currentIframeId() {
|
||||
return uniqueId;
|
||||
}
|
||||
@@ -48,6 +46,11 @@ export default class SkillsDisplayJS {
|
||||
}
|
||||
|
||||
attachTo(selectorOrElement) {
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info('SkillsClient::SkillsDisplayJS::attachTo: SkillsConfiguration is disabled Skills Display will not be initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`SkillsClient::SkillsDisplayJS::attaching to [${selectorOrElement ? selectorOrElement.toString() : selectorOrElement}]`);
|
||||
let iframeContainer = selectorOrElement;
|
||||
if (typeof selectorOrElement === 'string') {
|
||||
@@ -80,6 +83,7 @@ export default class SkillsDisplayJS {
|
||||
});
|
||||
const iframe = document.querySelector(`.${className}`);
|
||||
iframe.setAttribute('style', 'border: 0; height: 100%; width: 100%');
|
||||
iframe.setAttribute('aria-label', 'SkillTree Skills Display')
|
||||
|
||||
iframeContainer.height = 0;
|
||||
iframeContainer.style.height = '0px';
|
||||
@@ -195,7 +199,6 @@ export default class SkillsDisplayJS {
|
||||
errorMessage += ' and that your browser trusts the server\'s certificate';
|
||||
}
|
||||
errorMessage += `. skilltree service URL: ${this.configuration.serviceUrl}`;
|
||||
/* eslint-disable no-console */
|
||||
console.error(errorMessage);
|
||||
log.error(`SkillsClient::SkillsDisplayJS::${errorMessage}`, error);
|
||||
ErrorPageUtils.removeAllChildren(iframeContainer);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,10 @@ export default class SkillsLevelJS {
|
||||
}
|
||||
|
||||
attachTo(selectorOrElement) {
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info('SkillsLevelJS.js::attachTo: SkillsConfiguration is disabled Level will not be initialized');
|
||||
return;
|
||||
}
|
||||
log.info(`SkillsClient::SkillsLevelJS::attaching to [${selectorOrElement}]`);
|
||||
let skillLevelElement = selectorOrElement;
|
||||
if (typeof selectorOrElement === 'string') {
|
||||
@@ -51,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}` };
|
||||
@@ -60,7 +64,7 @@ export default class SkillsLevelJS {
|
||||
.then((result) => {
|
||||
this.setSkillLevel(result.data);
|
||||
SkillsReporter.addSuccessHandler(this.update.bind(this));
|
||||
log.info(`SkillsClient::SkillsLevelJS::recieved initial skill level [${result.data}]`);
|
||||
log.info(`SkillsClient::SkillsLevelJS::received initial skill level [${result.data}]`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,7 +32,6 @@ const callSuccessHandlers = (event) => {
|
||||
};
|
||||
|
||||
const callErrorHandlers = (event) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error reporting skill:', event);
|
||||
errorHandlerCache.forEach((it) => it(event));
|
||||
};
|
||||
@@ -139,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()}`);
|
||||
}
|
||||
@@ -182,6 +181,11 @@ const SkillsReporter = {
|
||||
this.maxRetryQueueSize = maxRetryQueueSize;
|
||||
this.maxRetryAttempts = maxRetryAttempts;
|
||||
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info('SkillsClient::SkillsReporter::configure: SkillsConfiguration is disabled. No reporting will occur');
|
||||
return;
|
||||
}
|
||||
|
||||
if (retryInterval != null) {
|
||||
// cancel existing RetryChecker, then start w/ passed in retryInterval
|
||||
this.cancelRetryChecker();
|
||||
@@ -191,14 +195,26 @@ const SkillsReporter = {
|
||||
},
|
||||
|
||||
addSuccessHandler(handler) {
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info('SkillsClient::SkillsReporter::addSuccessHandler: SkillsConfiguration is disabled handler is not added');
|
||||
return;
|
||||
}
|
||||
successHandlerCache.add(handler);
|
||||
log.info(`SkillsClient::SkillsReporter::added success handler [${handler ? handler.toString() : handler}]`);
|
||||
log.info(`SkillsClient::SkillsReporter::added success handler. successHandlerCache size [${successHandlerCache.size}]`);
|
||||
},
|
||||
addErrorHandler(handler) {
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info('SkillsClient::SkillsReporter::addErrorHandler: SkillsConfiguration is disabled handler is not added');
|
||||
return;
|
||||
}
|
||||
errorHandlerCache.add(handler);
|
||||
log.info(`SkillsClient::SkillsReporter::added error handler [${handler ? handler.toString() : handler}]`);
|
||||
log.info(`SkillsClient::SkillsReporter::added error handler. errorHandlerCache size [${errorHandlerCache.size}]`);
|
||||
},
|
||||
reportSkill(userSkillId, timestamp = null, isRetry = false, retryAttempt = undefined) {
|
||||
if (SkillsConfiguration.isDisabled()) {
|
||||
log.info(`SkillsClient::SkillsReporter::reportSkill: SkillsConfiguration is disabled. No reporting will occur for skillId=[${userSkillId}]`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
log.info(`SkillsClient::SkillsReporter::reporting skill [${userSkillId}] retryAttempt [${retryAttempt}]`);
|
||||
SkillsConfiguration.validate();
|
||||
if (retryIntervalId == null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import mock from 'xhr-mock';
|
||||
import SkillsService from '../src/SkillsService';
|
||||
import log from 'js-logger';
|
||||
|
||||
require('@babel/polyfill');
|
||||
|
||||
@@ -22,32 +23,14 @@ describe('OAuth auto redirect tests', () => {
|
||||
const mockServiceUrl = 'http://some.com';
|
||||
const mockProjectId = 'proj1';
|
||||
const authEndpoint = `${mockServiceUrl}/oauth2/authorization/gitlab`;
|
||||
const oldWindowLocation = window.location
|
||||
|
||||
beforeAll(() => {
|
||||
delete window.location
|
||||
|
||||
window.location = Object.defineProperties(
|
||||
{},
|
||||
{
|
||||
...Object.getOwnPropertyDescriptors(oldWindowLocation),
|
||||
assign: {
|
||||
configurable: true,
|
||||
value: jest.fn(),
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
afterAll(() => {
|
||||
// restore `window.location` to the `jsdom` `Location` object
|
||||
window.location = oldWindowLocation
|
||||
SkillsService.assignWindowLocation = jest.fn();
|
||||
})
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
const url = /.*\/api\/projects\/proj1\/skillsClientVersion/;
|
||||
window.location.assign.mockReset()
|
||||
SkillsService.assignWindowLocation.mockReset()
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
@@ -61,8 +44,24 @@ describe('OAuth auto redirect tests', () => {
|
||||
|
||||
await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId, true);
|
||||
|
||||
expect(window.location.assign).toHaveBeenCalledTimes(1)
|
||||
expect(window.location.assign).toHaveBeenCalledWith(`${authEndpoint}?skillsRedirectUri=http://localhost/`)
|
||||
expect(SkillsService.assignWindowLocation).toHaveBeenCalledTimes(1)
|
||||
expect(SkillsService.assignWindowLocation).toHaveBeenCalledWith(`${authEndpoint}?skillsRedirectUri=${encodeURIComponent('http://localhost/')}`)
|
||||
});
|
||||
|
||||
it('oauth redirect encodes the current location so it cannot add query parameters', async () => {
|
||||
const oauthTokenEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/token`;
|
||||
mock.get(oauthTokenEndpoint, (req, res) => res.status(401));
|
||||
|
||||
window.history.pushState({}, '', '/page?a=1&skillsRedirectUri=http://evil.example.com/');
|
||||
const injected = window.location.href;
|
||||
|
||||
await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId, true);
|
||||
|
||||
const [redirectedTo] = SkillsService.assignWindowLocation.mock.calls[0];
|
||||
expect(redirectedTo).toBe(`${authEndpoint}?skillsRedirectUri=${encodeURIComponent(injected)}`);
|
||||
// the injected separators must not survive as separators
|
||||
expect(redirectedTo.split('?')).toHaveLength(2);
|
||||
expect(redirectedTo).not.toContain('&skillsRedirectUri=');
|
||||
});
|
||||
|
||||
it('oauth does not auto redirect when oauthRedirect=false', async () => {
|
||||
@@ -73,7 +72,7 @@ describe('OAuth auto redirect tests', () => {
|
||||
await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId);
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch('Unable to authenticate');
|
||||
expect(window.location.assign).toHaveBeenCalledTimes(0);
|
||||
expect(SkillsService.assignWindowLocation).toHaveBeenCalledTimes(0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,8 +81,148 @@ describe('OAuth auto redirect tests', () => {
|
||||
mock.get(oauthTokenEndpoint, (req, res) => res.status(200).body('{"access_token": "token1"}'));
|
||||
|
||||
const result = await SkillsService.getAuthenticationToken(authEndpoint, mockServiceUrl, mockProjectId, true);
|
||||
expect(window.location.assign).toHaveBeenCalledTimes(0);
|
||||
expect(SkillsService.assignWindowLocation).toHaveBeenCalledTimes(0);
|
||||
expect(result).toMatch('token1');
|
||||
});
|
||||
|
||||
it('send log message', async () => {
|
||||
const logEndpoint = `${mockServiceUrl}/public/log`;
|
||||
const testMessage = 'Test log message';
|
||||
const testLevel = 'ERROR';
|
||||
|
||||
// Mock the POST request to the log endpoint
|
||||
mock.post(logEndpoint, (req, res) => {
|
||||
// Verify the request body contains expected data
|
||||
expect(req.body()).toContain('message');
|
||||
expect(req.body()).toContain(testMessage);
|
||||
expect(req.body()).toContain(testLevel);
|
||||
return res.status(200).body('{"success": true}');
|
||||
});
|
||||
|
||||
const result = await SkillsService.sendLogMessage(mockServiceUrl, [testMessage], { level: testLevel });
|
||||
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('send log message failure', async () => {
|
||||
const logEndpoint = `${mockServiceUrl}/public/log`;
|
||||
const testMessage = 'Test log message';
|
||||
const testLevel = 'ERROR';
|
||||
|
||||
// Mock a failed response
|
||||
mock.post(logEndpoint, (req, res) => res.status(500));
|
||||
|
||||
try {
|
||||
await SkillsService.sendLogMessage(mockServiceUrl, [testMessage], { level: testLevel });
|
||||
fail('Expected sendLogMessage to throw an error');
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('Unable to send client log message');
|
||||
expect(error.message).toContain('Received status [500]');
|
||||
}
|
||||
});
|
||||
|
||||
it('configure logging with enabled logging', async () => {
|
||||
const logEndpoint = `${mockServiceUrl}/public/log`;
|
||||
const testMessage = 'Test log message';
|
||||
|
||||
// Mock the POST request for log message
|
||||
mock.post(logEndpoint, (req, res) => res.status(200).body('{"success": true}'));
|
||||
|
||||
const response = {
|
||||
clientLib: {
|
||||
loggingEnabled: 'true',
|
||||
loggingLevel: 'INFO'
|
||||
}
|
||||
};
|
||||
|
||||
// Mock console.info to capture log output
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
SkillsService.configureLogging(mockServiceUrl, response);
|
||||
|
||||
// Trigger a log message to test the handler
|
||||
log.info(testMessage);
|
||||
|
||||
// Wait for async log message to be sent
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// Check that our test message was logged
|
||||
expect(consoleSpy.mock.calls.some(call =>
|
||||
call[0] === testMessage || (Array.isArray(call[0]) && call[0].includes(testMessage))
|
||||
)).toBe(true);
|
||||
consoleSpy.mockReset();
|
||||
});
|
||||
|
||||
it('configure logging with unknown level defaults to INFO and logs warning', async () => {
|
||||
const logEndpoint = `${mockServiceUrl}/public/log`;
|
||||
const response = {
|
||||
clientLib: {
|
||||
loggingEnabled: 'true',
|
||||
loggingLevel: 'UNKNOWN_LEVEL'
|
||||
}
|
||||
};
|
||||
|
||||
// Mock the POST request for log message
|
||||
mock.post(logEndpoint, (req, res) => res.status(200).body('{"success": true}'));
|
||||
|
||||
// Spy on the logger's warn method instead of console.warn
|
||||
const logWarnSpy = jest.spyOn(log, 'warn').mockImplementation();
|
||||
const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
|
||||
|
||||
SkillsService.configureLogging(mockServiceUrl, response);
|
||||
|
||||
// Verify the specific warning message was logged
|
||||
expect(logWarnSpy).toHaveBeenCalledWith(
|
||||
'SkillsClient::SkillService::Unknown log level [UNKNOWN_LEVEL], defaulting to INFO'
|
||||
);
|
||||
|
||||
// Verify that logging still works after defaulting to INFO
|
||||
log.info('Test message after defaulting');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
logWarnSpy.mockRestore();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('reportSkillsClientVersion success', async () => {
|
||||
const mockConfig = {
|
||||
getServiceUrl: () => mockServiceUrl,
|
||||
getProjectId: () => mockProjectId,
|
||||
isPKIMode: () => false,
|
||||
getAuthToken: () => 'test-token'
|
||||
};
|
||||
|
||||
const versionEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/skillsClientVersion`;
|
||||
const mockResponse = { success: true, version: '3.6.2' };
|
||||
|
||||
// Mock a successful response (200)
|
||||
mock.post(versionEndpoint, (req, res) => res.status(200).body(JSON.stringify(mockResponse)));
|
||||
|
||||
const result = await SkillsService.reportSkillsClientVersion(mockConfig);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('reportSkillsClientVersion failure', async () => {
|
||||
const mockConfig = {
|
||||
getServiceUrl: () => mockServiceUrl,
|
||||
getProjectId: () => mockProjectId,
|
||||
isPKIMode: () => false,
|
||||
getAuthToken: () => 'test-token'
|
||||
};
|
||||
|
||||
const versionEndpoint = `${mockServiceUrl}/api/projects/${mockProjectId}/skillsClientVersion`;
|
||||
|
||||
// Mock a failed response (e.g., 401)
|
||||
mock.post(versionEndpoint, (req, res) => res.status(401));
|
||||
|
||||
try {
|
||||
await SkillsService.reportSkillsClientVersion(mockConfig);
|
||||
fail('Expected reportSkillsClientVersion to throw an error');
|
||||
} catch (error) {
|
||||
expect(error.message).toBe('Unable to report skillsClientVersion. Received status [401]');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,325 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
describe('SkillsConfiguration', () => {
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
it('exists', () => {
|
||||
expect(SkillsConfiguration).not.toBeNull();
|
||||
});
|
||||
|
||||
it('throws an error SkillsConfiguration.configure() was never called', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.validate();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('isInitialized is false when error occurs in configure', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
it('calling configure more than once after failure will attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
mock.reset();
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(503);
|
||||
});
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(2);
|
||||
});
|
||||
|
||||
it('calling configure more than once without failure will NOT attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await flushPromises();
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
});
|
||||
|
||||
it('setAuthToken updates the authToken', async () => {
|
||||
const mockAuthToken = Math.random() + 1;
|
||||
SkillsConfiguration.configure({ authToken: mockAuthToken, projectId: 'projectId', serviceUrl: 'http://somewhere' });
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
const newAuthToken = Math.random() + 2;
|
||||
SkillsConfiguration.setAuthToken(newAuthToken);
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(newAuthToken);
|
||||
});
|
||||
|
||||
describe('initializing', () => {
|
||||
describe('isPKIMode', () => {
|
||||
it('isPKIMode is false if authenticator is NOT \'pki\'', async () => {
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isPKIMode is true if authenticator is \'pki\'', async () => {
|
||||
const mockAuthenticator = 'pki';
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('properly initializes variables', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
const mockServiceUrl = Math.random().toString();
|
||||
const mockProjectId = Math.random().toString();
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
const mockAuthToken = Math.random().toString();
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
projectId: mockProjectId,
|
||||
serviceUrl: mockServiceUrl,
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: mockAuthToken,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getProjectId()).toBe(mockProjectId);
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe(mockServiceUrl);
|
||||
expect(SkillsConfiguration.getAuthenticator()).toBe(mockAuthenticator);
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
});
|
||||
|
||||
it('notifies afterConfigure Promisees', (done) => {
|
||||
let resolveCount = 0;
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
resolveCount++;
|
||||
});
|
||||
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
resolveCount++;
|
||||
expect(resolveCount).toBe(2);
|
||||
done();
|
||||
});
|
||||
|
||||
SkillsConfiguration.configure({ authenticator: 'pki', projectId: 'proj', serviceUrl: 'http://somewhere' });
|
||||
});
|
||||
|
||||
it('trailing slash and spaces are removed from the serviceUrl', async () => {
|
||||
const authEndpoint = 'http://auth/';
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: ' http://some/ ',
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe('http://some');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('throws an error if projectId is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
it('throws an error if projectId is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: null,
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if serviceUrl is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: null,
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for serviceUrl=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if serviceUrl is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for serviceUrl=[undefined]');
|
||||
});
|
||||
|
||||
it('throws an error if authenticator is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
authenticator: 'null',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for authenticator=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if authenticator is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for authenticator=[undefined]');
|
||||
});
|
||||
|
||||
it('does not throw an error if everything is set', () => {
|
||||
const mockProjectId = Math.random().toString();
|
||||
const mockServiceUrl = Math.random().toString();
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
|
||||
const configObject = {
|
||||
projectId: mockProjectId,
|
||||
serviceUrl: mockServiceUrl,
|
||||
authenticator: mockAuthenticator,
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(configObject);
|
||||
|
||||
expect(() => {
|
||||
SkillsConfiguration.validate();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('reinitializes the afterConfigure promise' , () => {
|
||||
const oldPromise = SkillsConfiguration.afterConfigure();
|
||||
|
||||
SkillsConfiguration.logout();
|
||||
|
||||
const newPromise = SkillsConfiguration.afterConfigure();
|
||||
|
||||
expect(oldPromise).not.toBe(newPromise);
|
||||
});
|
||||
|
||||
it('clears the authToken', async () => {
|
||||
const mockAuthToken = Math.random().toString();;
|
||||
SkillsConfiguration.configure({
|
||||
authToken: mockAuthToken,
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
authenticator: 'http://auth',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
SkillsConfiguration.logout();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
import log from 'js-logger';
|
||||
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
describe('SkillsConfigurationInit', () => {
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
it('exists', () => {
|
||||
expect(SkillsConfiguration).not.toBeNull();
|
||||
});
|
||||
|
||||
it('throws an error SkillsConfiguration.configure() was never called', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.validate();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('isInitialized is false when error occurs in configure', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
it('calling configure more than once after failure will attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
mock.reset();
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(503);
|
||||
});
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await new Promise(process.nextTick);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await new Promise(process.nextTick);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(2);
|
||||
});
|
||||
|
||||
it('calling configure more than once without failure will NOT attempt to configure again', async () => {
|
||||
expect.assertions(6);
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
const config = {
|
||||
projectId: Math.random().toString(),
|
||||
serviceUrl: Math.random().toString(),
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: Math.random().toString(),
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await new Promise(process.nextTick);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
|
||||
SkillsConfiguration.configure(config);
|
||||
await new Promise(process.nextTick);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
expect(statusReqCount).toBe(1);
|
||||
});
|
||||
|
||||
it('setAuthToken updates the authToken', async () => {
|
||||
const mockAuthToken = Math.random() + 1;
|
||||
SkillsConfiguration.configure({
|
||||
authToken: mockAuthToken,
|
||||
projectId: 'projectId',
|
||||
serviceUrl: 'http://somewhere'
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
const newAuthToken = Math.random() + 2;
|
||||
SkillsConfiguration.setAuthToken(newAuthToken);
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(newAuthToken);
|
||||
});
|
||||
|
||||
it('properly initializes variables', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
const mockServiceUrl = Math.random().toString();
|
||||
const mockProjectId = Math.random().toString();
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
const mockAuthToken = Math.random().toString();
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
projectId: mockProjectId,
|
||||
serviceUrl: mockServiceUrl,
|
||||
authenticator: mockAuthenticator,
|
||||
authToken: mockAuthToken,
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.getProjectId()).toBe(mockProjectId);
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe(mockServiceUrl);
|
||||
expect(SkillsConfiguration.getAuthenticator()).toBe(mockAuthenticator);
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(true);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
});
|
||||
|
||||
it('notifies afterConfigure Promisees', (done) => {
|
||||
let resolveCount = 0;
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
resolveCount++;
|
||||
});
|
||||
|
||||
SkillsConfiguration.afterConfigure().then(() => {
|
||||
resolveCount++;
|
||||
expect(resolveCount).toBe(2);
|
||||
done();
|
||||
});
|
||||
|
||||
SkillsConfiguration.configure({authenticator: 'pki', projectId: 'proj', serviceUrl: 'http://somewhere'});
|
||||
});
|
||||
|
||||
it('trailing slash and spaces are removed from the serviceUrl', async () => {
|
||||
const authEndpoint = 'http://auth/';
|
||||
mock.get(authEndpoint, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: ' http://some/ ',
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.getServiceUrl()).toBe('http://some');
|
||||
});
|
||||
|
||||
it('configure with enabled=false exercises disabled logging path', () => {
|
||||
// Spy on the logger methods to verify they are called
|
||||
const logUseDefaultsSpy = jest.spyOn(log, 'useDefaults').mockImplementation();
|
||||
const logSetLevelSpy = jest.spyOn(log, 'setLevel').mockImplementation();
|
||||
const logInfoSpy = jest.spyOn(log, 'info').mockImplementation();
|
||||
|
||||
const result = SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://test.com',
|
||||
projectId: 'test-project',
|
||||
authenticator: 'http://test.com/auth',
|
||||
enabled: false
|
||||
});
|
||||
|
||||
// Verify the specific lines are exercised
|
||||
expect(logUseDefaultsSpy).toHaveBeenCalled();
|
||||
expect(logSetLevelSpy).toHaveBeenCalledWith(log.TRACE);
|
||||
expect(logInfoSpy).toHaveBeenCalledWith('SkillsConfiguration is disabled.');
|
||||
|
||||
// Verify it returns a resolved promise
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
|
||||
// Clean up spies
|
||||
logUseDefaultsSpy.mockRestore();
|
||||
logSetLevelSpy.mockRestore();
|
||||
logInfoSpy.mockRestore();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
describe('SkillsConfiguration', () => {
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
it('reinitializes the afterConfigure promise', () => {
|
||||
const oldPromise = SkillsConfiguration.afterConfigure();
|
||||
|
||||
SkillsConfiguration.logout();
|
||||
|
||||
const newPromise = SkillsConfiguration.afterConfigure();
|
||||
|
||||
expect(oldPromise).not.toBe(newPromise);
|
||||
});
|
||||
|
||||
it('clears the authToken', async () => {
|
||||
const mockAuthToken = Math.random().toString();
|
||||
;
|
||||
SkillsConfiguration.configure({
|
||||
authToken: mockAuthToken,
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
authenticator: 'http://auth',
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(mockAuthToken);
|
||||
|
||||
SkillsConfiguration.logout();
|
||||
|
||||
expect(SkillsConfiguration.getAuthToken()).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
describe('SkillsConfigurationPkiMode', () => {
|
||||
const flushPromises = () => new Promise(setImmediate);
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
it('isPKIMode is false if authenticator is NOT \'pki\'', async () => {
|
||||
const mockAuthenticator = Math.random().toString();
|
||||
mock.get(mockAuthenticator, (req, res) => res.status(200).body('{"access_token": "token"}'));
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isPKIMode is true if authenticator is \'pki\'', async () => {
|
||||
const mockAuthenticator = 'pki';
|
||||
|
||||
SkillsConfiguration.configure({
|
||||
authenticator: mockAuthenticator, projectId: 'proj', serviceUrl: 'http://somewhere',
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(SkillsConfiguration.isPKIMode()).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 mock from 'xhr-mock';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
describe('SkillsConfigurationValidation', () => {
|
||||
const mockStatusResponse = '{"status":"OK","clientLib":{"loggingEnabled":"false","loggingLevel":"DEBUG"}}';
|
||||
const mockVersionResponse = '{"success":true,"explanation":null}';
|
||||
let statusReqCount = 0;
|
||||
|
||||
// replace the real XHR object with the mock XHR object before each test
|
||||
beforeEach(() => {
|
||||
mock.setup();
|
||||
SkillsConfiguration.logout();
|
||||
const url = /.*\/api\/projects\/.*\/skillsClientVersion/;
|
||||
mock.post(url, (req, res) => res.status(200).body(mockVersionResponse));
|
||||
mock.get(/.*\/public\/status/, (req, res) => {
|
||||
statusReqCount++;
|
||||
return res.status(200).body(mockStatusResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// put the real XHR object back and clear the mocks after each test
|
||||
afterEach(() => {
|
||||
SkillsConfiguration.logout();
|
||||
mock.teardown();
|
||||
statusReqCount = 0;
|
||||
});
|
||||
|
||||
it('throws an error if projectId is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
|
||||
// when an error occurs during initialization isInitialized should be false, but wasConfigureCalled should be true
|
||||
expect(SkillsConfiguration.isInitialized()).toBe(false);
|
||||
expect(SkillsConfiguration.wasConfigureCalled()).toBe(true);
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[undefined]');
|
||||
});
|
||||
|
||||
it('throws an error if projectId is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: null,
|
||||
serviceUrl: 'http://somewhere',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for projectId=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if serviceUrl is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: null,
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for serviceUrl=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if serviceUrl is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
authenticator: 'pki',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for serviceUrl=[undefined]');
|
||||
});
|
||||
|
||||
it('throws an error if authenticator is set to null', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
authenticator: 'null',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for authenticator=[null]');
|
||||
});
|
||||
|
||||
it('throws an error if authenticator is not set', () => {
|
||||
expect(() => {
|
||||
SkillsConfiguration.configure({
|
||||
projectId: 'proj',
|
||||
serviceUrl: 'http://some',
|
||||
});
|
||||
}).toThrow('SkillTree: SkillsConfiguration.configure received invalid parameter for authenticator=[undefined]');
|
||||
});
|
||||
|
||||
it('does not throw an error if everything is set', () => {
|
||||
const mockProjectId = Math.random().toString();
|
||||
const mockServiceUrl = Math.random().toString();
|
||||
const mockAuthenticator = 'pki';
|
||||
|
||||
const configObject = {
|
||||
projectId: mockProjectId,
|
||||
serviceUrl: mockServiceUrl,
|
||||
authenticator: mockAuthenticator,
|
||||
};
|
||||
|
||||
SkillsConfiguration.configure(configObject);
|
||||
|
||||
expect(() => {
|
||||
SkillsConfiguration.validate();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
const Environment = require('jest-environment-jsdom');
|
||||
const Environment = require('jest-environment-jsdom').TestEnvironment;
|
||||
|
||||
/**
|
||||
* A custom environment to set the TextEncoder that is required by @stomp/stompjs.
|
||||
*/
|
||||
module.exports = class CustomTestEnvironment extends Environment {
|
||||
constructor(config, context) {
|
||||
super(config, context);
|
||||
}
|
||||
|
||||
async setup() {
|
||||
await super.setup();
|
||||
if (typeof this.global.TextEncoder === 'undefined') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import axios from 'axios';
|
||||
|
||||
import SkillsDisplayJS from '../../src/display/SkillsDisplayJS';
|
||||
import ErrorPageUtils from '../../src/display/ErrorPageUtils';
|
||||
import SkillsConfiguration from '../../src/config/SkillsConfiguration';
|
||||
|
||||
jest.mock('../../src/display/ErrorPageUtils.js');
|
||||
jest.mock('postmate');
|
||||
@@ -80,6 +81,7 @@ describe('SkillsDisplayJS', () => {
|
||||
let realDocument;
|
||||
|
||||
beforeEach(() => {
|
||||
SkillsConfiguration.enabled = true
|
||||
realDocument = global.document;
|
||||
global.document.querySelector = jest.fn();
|
||||
});
|
||||
@@ -480,6 +482,7 @@ describe('SkillsDisplayJS', () => {
|
||||
isSummaryOnly: false,
|
||||
projectId: mockProjectId,
|
||||
serviceUrl: mockServiceUrl,
|
||||
parentPath: '/',
|
||||
};
|
||||
mockVersion = `${Math.random()}`;
|
||||
|
||||
@@ -495,6 +498,7 @@ describe('SkillsDisplayJS', () => {
|
||||
authenticator: 'pki',
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
parentPath: '/'
|
||||
};
|
||||
|
||||
const client = new SkillsDisplayJS({
|
||||
@@ -536,6 +540,7 @@ describe('SkillsDisplayJS', () => {
|
||||
authenticator: mockAuthenticator,
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
parentPath: '/'
|
||||
};
|
||||
const client = new SkillsDisplayJS({
|
||||
options,
|
||||
@@ -576,6 +581,7 @@ describe('SkillsDisplayJS', () => {
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
internalBackButton: false,
|
||||
parentPath: '/'
|
||||
};
|
||||
const client = new SkillsDisplayJS({
|
||||
options,
|
||||
@@ -616,6 +622,7 @@ describe('SkillsDisplayJS', () => {
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
internalBackButton: true,
|
||||
parentPath: '/'
|
||||
};
|
||||
const client = new SkillsDisplayJS({
|
||||
options,
|
||||
@@ -655,6 +662,7 @@ describe('SkillsDisplayJS', () => {
|
||||
authenticator: mockAuthenticator,
|
||||
serviceUrl: mockServiceUrl,
|
||||
projectId: mockProjectId,
|
||||
parentPath: '/'
|
||||
};
|
||||
const client = new SkillsDisplayJS({
|
||||
options,
|
||||
@@ -693,7 +701,7 @@ describe('SkillsDisplayJS', () => {
|
||||
describe('construction', () => {
|
||||
describe('options', () => {
|
||||
it('accepts options in the constructor', () => {
|
||||
const mockOptions = { authenticator: 'option' };
|
||||
const mockOptions = { authenticator: 'option', parentPath: 'non-default/' };
|
||||
const client = new SkillsDisplayJS({
|
||||
options: mockOptions,
|
||||
});
|
||||
@@ -702,14 +710,14 @@ describe('SkillsDisplayJS', () => {
|
||||
|
||||
it('supports null or undefined being passed', () => {
|
||||
let client = new SkillsDisplayJS({ options: null });
|
||||
expect(client.options).toEqual({});
|
||||
expect(client.options).toEqual({ parentPath: '/' });
|
||||
client = new SkillsDisplayJS({ options: undefined });
|
||||
expect(client.options).toEqual({});
|
||||
expect(client.options).toEqual({ parentPath: '/'} );
|
||||
});
|
||||
|
||||
it('supports empty constructor', () => {
|
||||
it('supports empty constructor, defaults parentPath to window.location.pathname', () => {
|
||||
const client = new SkillsDisplayJS();
|
||||
expect(client.options).toEqual({});
|
||||
expect(client.options).toEqual({ parentPath: '/'} );
|
||||
});
|
||||
|
||||
it('accepts valid options', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -68,6 +68,7 @@ describe('authFormTests()', () => {
|
||||
projectId: mockProjectId,
|
||||
authenticator: authEndpoint,
|
||||
});
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
const mockUserSkillId = 'skill1';
|
||||
const url = `${mockServiceUrl}/api/projects/${mockProjectId}/skills/${mockUserSkillId}`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 SkillTree
|
||||
* Copyright 2026 SkillTree
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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', () => {
|
||||
@@ -162,7 +171,9 @@ describe('userSkillsService', () => {
|
||||
|
||||
MockXMLHttpRequest.onreadystatechange();
|
||||
|
||||
promise.finally(() => {
|
||||
promise.catch(() => {
|
||||
// error is ok here
|
||||
}).finally(() => {
|
||||
expect(handler1).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
expect(handler2).toHaveBeenCalledWith(JSON.parse(mockError));
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Error reporting skill:', JSON.parse(mockError));
|
||||
|
||||
Reference in new issue
Block a user