Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

execute automated external browser tests #970

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/parameters_aws_auth_tests.json.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Œ ÚÿPþ™tÿÒÀ°„än4í"û­¦KxÉ{3Ëq—¾rºÔÖÝ*V}hý I§V_FÕ-…я[‹‘ôqìéx·/ÝÒ,»0¸NÀ£j„°Äp:kŽ~±0öí‹Þ;0¶fpëttäõüQF)qÆv5ñÊsÙ¨»â°þÀ>P~TyG;Jÿb¶ë®àoŒÅ/S’Œú’Ç ãu¦ÕNa¿nêŽlNÿÛátG¥Ìîñz–-óTž–’”á§ìµõ<S릃Nbù³ãjbÇÏYle¢? 9‡¹ë¼½VçHÓ¡<y˜“
¤Æ D[jï‚sE÷?J^ßyžâùúuÒŸm_§kLË™èXv¾¾žâ=zYÖb^9Ù$2Æ‹cÇÕeCŸ]2¯!ªðÔ.®ô¼¢2YÇyû/þ¢H'uÁ.húÌo6³Ãj}ª3ÑÞIb<wÂC_rù¦3ý]ýgÃö2ÃþíAݹ³oF
éÔ™üýèä^Œ
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
*.swp
.idea
.git
parameters.json
parameters*.json
snowflake-sdk-*.tgz
dist
junit*.xml
Expand Down
52 changes: 35 additions & 17 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ timestamps {
stage('Build') {
withCredentials([
usernamePassword(credentialsId: '063fc85b-62a6-4181-9d72-873b43488411', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY'),
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c',variable: 'NEXUS_PASSWORD')
]) {
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD')
]) {
sh '''\
|#!/bin/bash -e
|export GIT_BRANCH=${GIT_BRANCH}
Expand All @@ -23,18 +23,36 @@ timestamps {
'''.stripMargin()
}
}
params = [
string(name: 'svn_revision', value: 'main'),
string(name: 'branch', value: 'main'),
string(name: 'client_git_commit', value: scmInfo.GIT_COMMIT),
string(name: 'client_git_branch', value: scmInfo.GIT_BRANCH),
string(name: 'TARGET_DOCKER_TEST_IMAGE', value: 'nodejs-chainguard-node18'),
string(name: 'parent_job', value: env.JOB_NAME),
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
]
stage('Test') {
build job: 'RT-LanguageNodeJS-PC',parameters: params
}

parallel(
'Test': {
stage('Test') {
def params = [
string(name: 'svn_revision', value: 'main'),
string(name: 'branch', value: 'main'),
string(name: 'client_git_commit', value: scmInfo.GIT_COMMIT),
string(name: 'client_git_branch', value: scmInfo.GIT_BRANCH),
string(name: 'TARGET_DOCKER_TEST_IMAGE', value: 'nodejs-chainguard-node18'),
string(name: 'parent_job', value: env.JOB_NAME),
string(name: 'parent_build_number', value: env.BUILD_NUMBER)
]
build job: 'RT-LanguageNodeJS-PC', parameters: params
}
},
'Test Authentication': {
stage('Test Authentication') {
withCredentials([
string(credentialsId: 'a791118f-a1ea-46cd-b876-56da1b9bc71c', variable: 'NEXUS_PASSWORD'),
string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET')
]) {
sh '''\
|#!/bin/bash -e
|$WORKSPACE/ci/test_authentication.sh
'''.stripMargin()
}
}
}
)
}
}

Expand All @@ -61,7 +79,7 @@ pipeline {
}

def wgetUpdateGithub(String state, String folder, String targetUrl, String seconds) {
def ghURL = "https://api.github.com/repos/snowflakedb/snowflake-connector-nodejs/statuses/$COMMIT_SHA_LONG"
def data = JsonOutput.toJson([state: "${state}", context: "jenkins/${folder}",target_url: "${targetUrl}"])
sh "wget ${ghURL} --spider -q --header='Authorization: token $GIT_PASSWORD' --post-data='${data}'"
def ghURL = "https://api.github.com/repos/snowflakedb/snowflake-connector-nodejs/statuses/$COMMIT_SHA_LONG"
def data = JsonOutput.toJson([state: "${state}", context: "jenkins/${folder}", target_url: "${targetUrl}"])
sh "wget ${ghURL} --spider -q --header='Authorization: token $GIT_PASSWORD' --post-data='${data}'"
}
8 changes: 8 additions & 0 deletions ci/container/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash -e

set -o pipefail

AUTH_PARAMETER_FILE=./.github/workflows/parameters_aws_auth_tests.json
eval $(jq -r '.authtestparams | to_entries | map("export \(.key)=\(.value|tostring)")|.[]' $AUTH_PARAMETER_FILE)

npm run test:authentication
2 changes: 1 addition & 1 deletion ci/container/test_component.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ if [[ -z "$GITHUB_ACTIONS" ]]; then
fi

echo "[INFO] Running Tests: Test result: $WORKSPACE/junit.xml"
if ! ${MOCHA_CMD[@]} "$SOURCE_ROOT/test/**/*.js"; then
if ! ${MOCHA_CMD[@]} 'test/{unit,integration}/**/*.js'; then
echo "[ERROR] Test failed"
[[ -f "$WORKSPACE/junit.xml" ]] && cat $WORKSPACE/junit.xml
exit 1
Expand Down
14 changes: 14 additions & 0 deletions ci/test_authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash -e

set -o pipefail
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export WORKSPACE=${WORKSPACE:-/tmp}

gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETERS_SECRET" --output $THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json "$THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json.gpg"

docker run \
-v $(cd $THIS_DIR/.. && pwd):/mnt/host \
-v $WORKSPACE:/mnt/workspace \
--rm \
nexus.int.snowflakecomputing.com:8086/docker/snowdrivers-test-external-browser:1 \
"/mnt/host/ci/container/test_authentication.sh"
2 changes: 1 addition & 1 deletion ci/test_windows.bat
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ start /b python hang_webserver.py 12345 > hang_webserver.out 2>&1
popd

echo [INFO] Testing
cmd /c node_modules\.bin\mocha --timeout %TIMEOUT% --recursive --full-trace --color --reporter spec test/**/*.js
cmd /c node_modules\.bin\mocha --timeout %TIMEOUT% --recursive --full-trace --color --reporter spec \"test/{unit,integration}/**/*.js\"
if %ERRORLEVEL% NEQ 0 (
echo [ERROR] failed to run mocha
exit /b 1
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@
"lint:check:all:errorsOnly": "npm run lint:check:all -- --quiet",
"lint:fix": "eslint --fix",
"test": "mocha -timeout 180000 --recursive --full-trace test/unit/**/*.js test/unit/*.js",
"test:authentication": "mocha --exit -timeout 180000 --recursive --full-trace test/authentication/**/*.js test/authentication/*.js",
"test:integration": "mocha -timeout 180000 --recursive --full-trace test/integration/**/*.js test/integration/*.js",
"test:single": "mocha -timeout 180000 --full-trace",
"test:system": "mocha -timeout 180000 --recursive --full-trace system_test/*.js",
"test:unit": "mocha -timeout 180000 --recursive --full-trace test/unit/**/*.js test/unit/*.js",
"test:unit:coverage": "nyc npm run test:unit",
"test:ci": "mocha -timeout 180000 --recursive --full-trace test/**/*.js",
"test:ci": "mocha -timeout 180000 --recursive --full-trace 'test/{unit,integration}/**/*.js'",
"test:ci:coverage": "nyc npm run test:ci",
"test:ci:withSystemTests": "mocha -timeout 180000 --recursive --full-trace test/**/*.js system_test/*.js",
"test:ci:withSystemTests": "mocha -timeout 180000 --recursive --full-trace 'test/{unit,integration}/**/*.js' system_test/*.js",
"test:ci:withSystemTests:coverage": "nyc npm run test:ci:withSystemTests",
"test:manual": "mocha -timeout 180000 --full-trace --full-trace test/integration/testManualConnection.js"
},
Expand Down
30 changes: 30 additions & 0 deletions test/authentication/connectionParameters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const snowflakeAuthTestProtocol = process.env.SNOWFLAKE_AUTH_TEST_PROTOCOL;
const snowflakeAuthTestHost = process.env.SNOWFLAKE_AUTH_TEST_HOST;
const snowflakeAuthTestPort = process.env.SNOWFLAKE_AUTH_TEST_PORT;
const snowflakeAuthTestAccount = process.env.SNOWFLAKE_AUTH_TEST_ACCOUNT;
const snowflakeAuthTestRole = process.env.SNOWFLAKE_AUTH_TEST_ROLE;
const snowflakeTestBrowserUser = process.env.SNOWFLAKE_AUTH_TEST_BROWSER_USER;
const snowflakeAuthTestOktaPass = process.env.SNOWFLAKE_AUTH_TEST_OKTA_PASS;
const snowflakeAuthTestDatabase = process.env.SNOWFLAKE_AUTH_TEST_DATABASE;
const snowflakeAuthTestWarehouse = process.env.SNOWFLAKE_AUTH_TEST_WAREHOUSE;
const snowflakeAuthTestSchema = process.env.SNOWFLAKE_AUTH_TEST_SCHEMA;

const accessUrlAuthTests = snowflakeAuthTestProtocol + '://' + snowflakeAuthTestHost + ':' +
snowflakeAuthTestPort;

const externalBrowser =
{
accessUrl: accessUrlAuthTests,
username: snowflakeTestBrowserUser,
account: snowflakeAuthTestAccount,
role: snowflakeAuthTestRole,
host: snowflakeAuthTestHost,
warehouse: snowflakeAuthTestWarehouse,
database: snowflakeAuthTestDatabase,
schema: snowflakeAuthTestSchema,
authenticator: 'EXTERNALBROWSER'
};

exports.externalBrowser = externalBrowser;
exports.snowflakeTestBrowserUser = snowflakeTestBrowserUser;
exports.snowflakeAuthTestOktaPass = snowflakeAuthTestOktaPass;
107 changes: 107 additions & 0 deletions test/authentication/testExternalBrowser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const snowflake = require('../../lib/snowflake');
const assert = require('assert');
const testUtil = require('../integration/testUtil');
const connParameters = require('./connectionParameters');
const { exec, spawn } = require('child_process');

describe('External browser authentication tests', function () {
const provideBrowserCredentialsPath = '/externalbrowser/provideBrowserCredentials.js';
const login = connParameters.snowflakeTestBrowserUser;
const password = connParameters.snowflakeAuthTestOktaPass;
let connection;

describe('External browser tests', async () => {
before(async () => {
await cleanBrowserProcesses();
});

afterEach(async () => {
await cleanBrowserProcesses();
if (connection !== undefined && connection.isUp()) {
await testUtil.destroyConnectionAsync(connection);
}
});

it('Successful connection', async () => {
const connectionOption = { ...connParameters.externalBrowser, clientStoreTemporaryCredential: false };
connection = await snowflake.createConnection(connectionOption);
const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'success', login, password], 15000);
await Promise.allSettled([connection.connectAsync(function () {}), provideCredentialsPromise]);
await verifyConnectionIsUp(connection);
});

it('Wrong credentials', async () => {
const login = 'itsnotanaccount.com';
const password = 'fakepassword';
const connectionOption = { ...connParameters.externalBrowser, browserActionTimeout: 5000, clientStoreTemporaryCredential: false };
connection = await snowflake.createConnection(connectionOption);

const provideCredentialsPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'fail', login, password]);
await Promise.allSettled([connection.connectAsync(function () {}), provideCredentialsPromise]);
await verifyConnectionIsNotUp(connection);
});

it('External browser timeout', async () => {
const connectionOption = { ...connParameters.externalBrowser, browserActionTimeout: 100, clientStoreTemporaryCredential: false };
connection = await snowflake.createConnection(connectionOption);

const connectToBrowserPromise = execWithTimeout('node', [provideBrowserCredentialsPath, 'timeout']);
await Promise.allSettled([connection.connectAsync(function () {}), connectToBrowserPromise]);
await verifyConnectionIsNotUp(connection);
});
});
});

async function cleanBrowserProcesses() {
exec('pkill -f chromium');
exec('pkill -f xdg-open');
}

async function verifyConnectionIsUp(connection) {
assert.ok(await connection.isValidAsync());
await testUtil.executeCmdAsync(connection, 'Select 1');
}

async function verifyConnectionIsNotUp(connection) {
try {
assert(!(await connection.isValidAsync()));
await testUtil.executeCmdAsync(connection, 'Select 1');
assert.fail('Expected error was not thrown');
} catch (error) {
assert.strictEqual(error.message, 'Unable to perform operation because a connection was never established.');
}
}

function execWithTimeout(command, args, timeout = 5000) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { shell: true });

let stdout = '';
let stderr = '';

child.stdout.on('data', (data) => {
stdout += data;
});

child.stderr.on('data', (data) => {
stderr += data;
});

child.on('error', (err) => {
reject(err);
});

child.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Provide browser credentials process exited with code: ${code}, error: ${stderr}`));
} else {
resolve({ stdout, stderr });
}
});

setTimeout(() => {
child.kill();
reject(new Error('Provide browser credentials process timed out'));
}, timeout);
});
}
65 changes: 0 additions & 65 deletions test/integration/testManualConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,71 +13,6 @@ const JsonCredentialManager = require('../../lib/authentication/secure_storage/j

if (process.env.RUN_MANUAL_TESTS_ONLY === 'true') {
describe('Run manual tests', function () {
describe('Connection test - external browser', function () {
it('Simple Connect', function (done) {
const connection = snowflake.createConnection(
connOption.externalBrowser
);

connection.connectAsync(function (err, connection) {
try {
assert.ok(connection.isUp(), 'not active');
testUtil.destroyConnection(connection, function () {
try {
assert.ok(!connection.isUp(), 'not active');
done();
} catch (err) {
done(err);
}
});
} catch (err) {
done(err);
}
});
});

it('Connect - external browser timeout', function (done) {
const connection = snowflake.createConnection(
connOption.externalBrowserWithShortTimeout
);

connection.connectAsync(function (err) {
try {
const browserActionTimeout =
connOption.externalBrowserWithShortTimeout.browserActionTimeout;
assert.ok(
err,
`Browser action timed out after ${browserActionTimeout} ms.`
);
done();
} catch (err) {
done(err);
}
});
});

it('Mismatched Username', function (done) {
const connection = snowflake.createConnection(
connOption.externalBrowserMismatchUser
);
connection.connectAsync(function (err) {
try {
assert.ok(
err,
'Logged in with different user than one on connection string'
);
assert.equal(
'The user you were trying to authenticate as differs from the user currently logged in at the IDP.',
err['message']
);
done();
} catch (err) {
done(err);
}
});
});
});

describe('Connection - ID Token authenticator', function () {
const connectionOption = { ...connOption.externalBrowser, clientStoreTemporaryCredential: true };
const key = Util.buildCredentialCacheKey(connectionOption.host, connectionOption.username, 'ID_TOKEN');
Expand Down
Loading