diff --git a/.github/workflows/npm-test.yml b/.github/workflows/npm-test.yml new file mode 100644 index 0000000..c5337b4 --- /dev/null +++ b/.github/workflows/npm-test.yml @@ -0,0 +1,16 @@ +name: 'npm test' +on: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.3 + - name: Setup Node.js environment + uses: actions/setup-node@v4.1.0 + with: + node-version: 18 + - run: npm ci + - run: npm test \ No newline at end of file diff --git a/.github/workflows/stackql-assert.yml b/.github/workflows/stackql-assert-test.yml similarity index 80% rename from .github/workflows/stackql-assert.yml rename to .github/workflows/stackql-assert-test.yml index e35cc98..eaf6851 100644 --- a/.github/workflows/stackql-assert.yml +++ b/.github/workflows/stackql-assert-test.yml @@ -1,4 +1,4 @@ -name: 'StackQL Assert' +name: 'stackql-assert' on: push: @@ -15,7 +15,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.4 + + # + # Pull required providers + # + - name: pull required providers + uses: stackql/stackql-exec@v2.2.3 + with: + is_command: true + query: "REGISTRY PULL google; REGISTRY PULL github" # # Example `test_query` with `expected_rows` @@ -24,10 +33,9 @@ jobs: uses: ./ with: test_query: | - REGISTRY PULL google; SELECT name FROM google.compute.instances - WHERE project = 'stackql-demo' AND zone = 'australia-southeast1-a' AND name = 'stackql-demo-001'; + WHERE project = 'stackql-integration-tests' AND zone = 'australia-southeast1-a' and name = 'stackql-test-001'; expected_rows: 1 env: GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} @@ -39,7 +47,7 @@ jobs: uses: ./ with: test_query_file_path: './.github/workflows/workflow_scripts/google-example.iql' - expected_results_str: '[{"name":"stackql-demo-001"}]' + expected_results_str: '[{"name":"stackql-test-001"}]' env: GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} @@ -60,7 +68,7 @@ jobs: # # Example `test_query_file_path` with `expected_rows` supplying `vars` using `jsonnet` config provided using `data_file_path` # - - name: Use test query string and expected results file, with auth object + - name: Use test query file with a jsonnet data file sourcing external vars and an expected row count uses: ./ with: test_query_file_path: './.github/workflows/workflow_scripts/github-example.iql' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index ad90499..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 'Build and Test' -on: - push: - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js 16 - uses: actions/setup-node@v3 - with: - node-version: 16.x - - run: npm ci - - run: npm test - \ No newline at end of file diff --git a/.github/workflows/workflow_scripts/github-example.iql b/.github/workflows/workflow_scripts/github-example.iql index 68dadb5..5dc84fb 100644 --- a/.github/workflows/workflow_scripts/github-example.iql +++ b/.github/workflows/workflow_scripts/github-example.iql @@ -1,2 +1 @@ -REGISTRY PULL github; SELECT name FROM github.repos.repos WHERE org = '{{ .org }}' AND name = '{{ .repo }}'; diff --git a/.github/workflows/workflow_scripts/google-example-inline-jsonnet-results.json b/.github/workflows/workflow_scripts/google-example-inline-jsonnet-results.json index 8f652aa..ed8d431 100644 --- a/.github/workflows/workflow_scripts/google-example-inline-jsonnet-results.json +++ b/.github/workflows/workflow_scripts/google-example-inline-jsonnet-results.json @@ -1 +1 @@ -[{"name":"stackql-demo-001"}] \ No newline at end of file +[{"name":"stackql-test-001"}] \ No newline at end of file diff --git a/.github/workflows/workflow_scripts/google-example-inline-jsonnet.iql b/.github/workflows/workflow_scripts/google-example-inline-jsonnet.iql index bceabe0..e42aade 100644 --- a/.github/workflows/workflow_scripts/google-example-inline-jsonnet.iql +++ b/.github/workflows/workflow_scripts/google-example-inline-jsonnet.iql @@ -8,4 +8,4 @@ local zone = std.extVar("GOOGLE_ZONE"); >>> SELECT name FROM google.compute.instances -WHERE project = '{{ .project }}' and zone = '{{ .zone }}' and name = 'stackql-demo-001'; \ No newline at end of file +WHERE project = '{{ .project }}' and zone = '{{ .zone }}' and name = 'stackql-test-001'; \ No newline at end of file diff --git a/.github/workflows/workflow_scripts/google-example.iql b/.github/workflows/workflow_scripts/google-example.iql index a10d1cf..acbed77 100644 --- a/.github/workflows/workflow_scripts/google-example.iql +++ b/.github/workflows/workflow_scripts/google-example.iql @@ -1,3 +1,3 @@ SELECT name FROM google.compute.instances -WHERE project = 'stackql-demo' AND zone = 'australia-southeast1-a' AND name = 'stackql-demo-001'; \ No newline at end of file +WHERE project = 'stackql-integration-tests' AND zone = 'australia-southeast1-a' AND name = 'stackql-test-001'; \ No newline at end of file diff --git a/README.md b/README.md index bad94d3..4a4616d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,26 @@ +[![StackQL Assert](https://github.com/stackql/stackql-assert/actions/workflows/stackql-assert-test.yml/badge.svg)](https://github.com/stackql/stackql-assert/actions/workflows/stackql-assert-test.yml) + # stackql-assert The `stackql/stackql-assert` action is an composite action that runs a `stackql` query and checks if the result matches an expected result # Usage +> This action uses the [setup-stackql](https://github.com/marketplace/actions/setup-stackql) and [stackql-exec](https://github.com/marketplace/actions/stackql-exec) actions + ## Provider Authentication Authentication to StackQL providers is done via environment variables source from GitHub Actions Secrets. To learn more about authentication, see the setup instructions for your provider or providers at the [StackQL Provider Registry Docs](https://stackql.io/registry). ## Inputs -- `test_query` - stackql query to execute **(need to supply either `test_query` or `test_query_file_path`)** -- `test_query_file_path` - stackql query file to execute **(need to supply either `test_query` or `test_query_file_path`)** -- `data_file_path` - (optional) path to data file to pass to the stackql query preprocessor (`json` or `jsonnet`) -- `vars` - (optional) comma delimited list of variables to pass to the stackql query preprocessor (supported with `jsonnet` config blocks or `jsonnet` data files only), accepts `var1=val1,var2=val2`, can be used to source environment variables into stackql queries -- `expected_rows` - (optional) Expected number of rows in the result. -- `expected_results_str` - (optional) Expected result (`json`) from executing test query, support object string (overrides `expected_results_file_path`) -- `expected_results_file_path` - (optional) Results file (`json`) that stores expected result, json is support -- `auth_obj_path` - (optional) the path of json file that stores stackql AUTH string **(only required when using non-standard environment variable names)** -- `auth_str` - (optional) stackql AUTH string **(only required when using non-standard environment variable names)** +- **`test_query`** - stackql query to execute *(need to supply either `test_query` or `test_query_file_path`)* +- **`test_query_file_path`** - stackql query file to execute *(need to supply either `test_query` or `test_query_file_path`)* +- **`data_file_path`** - (optional) path to data file to pass to the stackql query preprocessor (`json` or `jsonnet`) +- **`vars`** - (optional) comma delimited list of variables to pass to the stackql query preprocessor (supported with `jsonnet` config blocks or `jsonnet` data files only), accepts `var1=val1,var2=val2`, can be used to source environment variables into stackql queries +- **`expected_rows`** - (optional) Expected number of rows in the result. +- **`expected_results_str`** - (optional) Expected result (`json`) from executing test query, support object string (overrides `expected_results_file_path`) +- **`expected_results_file_path`** - (optional) Results file (`json`) that stores expected result, json is support +- **`auth_obj_path`** - (optional) the path of json file that stores stackql AUTH string *(only required when using non-standard environment variable names)* +- **`auth_str`** - (optional) stackql AUTH string *(only required when using non-standard environment variable names)* **__NOTE:__ one of `expected_rows`, `expected_results_str` or `expected_results_file_path` is required** @@ -28,7 +32,7 @@ Authentication to StackQL providers is done via environment variables source fro ## Expected Result - Use `expected_results_str` or `expected_results_file_path` or `expected_rows` to pass the expected result to the action. The expected result (`expected_results_str` or `expected_results_file_path`) should be a valid `json` object. The action will compare the result with the expected result. If the result is not the same as the expected result, the action will fail the step. - Either `expected_results_str` or `expected_results_file_path` or `expected_rows` are required. If `expected_results_str` and `expected_results_file_path` are provided, `expected_results_str` will be used. -- Expected result example can be found in [example workflow](./.github/workflows/stackql-assert.yml) and [example .json file](./.github/workflows/workflow_scripts) +- Expected result example can be found in [example workflow](./.github/workflows/stackql-assert-test.yml) and [example .json file](./.github/workflows/workflow_scripts) ## Examples The following excerpts from a GitHub Actions workflow demonstrate how to use the `stackql/stackql-assert` action. @@ -40,7 +44,6 @@ The following excerpts from a GitHub Actions workflow demonstrate how to use the uses: ./ with: test_query: | - REGISTRY PULL google; SELECT name FROM google.compute.instances WHERE project = 'stackql-demo' AND zone = 'australia-southeast1-a' AND name = 'stackql-demo-001'; @@ -82,8 +85,8 @@ The following excerpts from a GitHub Actions workflow demonstrate how to use the - name: Use test query string and expected results file, with auth object uses: ./ with: - test_query_file_path: './.github/workflows/workflow_scripts/google-example.iql' - data_file_path: './.github/workflows/workflow_scripts/google-example-data.jsonnet' + test_query_file_path: './.github/workflows/workflow_scripts/github-example.iql' + data_file_path: './.github/workflows/workflow_scripts/github-example-data.jsonnet' vars: TEST_ORG=${{ env.TEST_ORG }},TEST_REPO=${{ env.TEST_REPO }} expected_rows: 1 env: diff --git a/action.yml b/action.yml index f316e48..1b2fc78 100644 --- a/action.yml +++ b/action.yml @@ -1,106 +1,69 @@ -name: 'StackQL Studios - StackQL Assert' -description: 'run StackQL query to test and audit your infrastructure.' +name: 'stackql-assert' +description: 'Run StackQL query to test and audit your infrastructure.' author: 'Yuncheng Yang, StackQL Studios' inputs: test_query: - description: stackql query to execute (need to supply either test_query or test_query_file_path) + description: 'StackQL query to execute (supply either test_query or test_query_file_path)' required: false test_query_file_path: - description: stackql query file to execute (need to supply either test_query or test_query_file_path) + description: 'StackQL query file to execute (supply either test_query or test_query_file_path)' required: false data_file_path: - description: path to data file to pass to the stackql query preprocessor (json or jsonnet file) + description: 'Path to data file to pass to the StackQL query preprocessor (JSON or Jsonnet file)' required: false vars: - description: comma delimited list of variables to pass to the stackql query preprocessor (supported with jsonnet config blocks or jsonnet data files only), accepts 'var1=val1,var2=val2', can be used to source environment variables into stackql queries + description: 'Comma delimited list of variables to pass to the StackQL query preprocessor (supported with Jsonnet config blocks or Jsonnet data files only)' required: false - expected_rows: - description: expected number of rows from executing test query + expected_rows: + description: 'Expected number of rows from executing test query' required: false expected_results_str: - description: expected result (as a json string) from executing test query, overrides expected_results_file_path + description: 'Expected result (as a JSON string) from executing test query, overrides expected_results_file_path' required: false expected_results_file_path: - description: json file with the expected result + description: 'JSON file with the expected result' required: false auth_obj_path: - description: the path of json file that stores stackql AUTH string (only required when using non-standard environment variable names) + description: 'Path of JSON file that stores StackQL AUTH string (only required when using non-standard environment variable names)' required: false auth_str: - description: stackql AUTH string (only required when using non-standard environment variable names) + description: 'StackQL AUTH string (only required when using non-standard environment variable names)' required: false runs: using: "composite" steps: - - name: Check StackQL is installed and set output - id: check-stackql - shell: bash - run: | - if command -v stackql &> /dev/null; then - echo "stackql_installed=true" >> $GITHUB_OUTPUT - else - echo "stackql_installed=false" >> $GITHUB_OUTPUT - fi - - - name: Setup StackQL - uses: stackql/setup-stackql@v1.2.0 - if: ${{steps.check-stackql.outputs.stackql_installed == 'false'}} + uses: stackql/setup-stackql@v2.2.3 with: use_wrapper: true - - name: Setup auth - id: setup-auth - uses: actions/github-script@v6 + - name: Execute StackQL Command + id: exec-query + uses: stackql/stackql-exec@v2.2.3 with: - script: | - const path = require('path'); - const utilsPath = path.join(process.env.GITHUB_ACTION_PATH, 'lib', 'utils.js') - const {setupAuth} = require(utilsPath) - setupAuth(core) - env: - AUTH_FILE_PATH: ${{ inputs.auth_obj_path }} - AUTH_STR: ${{inputs.auth_str}} + query: ${{ inputs.test_query }} + query_file_path: ${{ inputs.test_query_file_path }} + data_file_path: ${{ inputs.data_file_path }} + vars: ${{ inputs.vars }} + auth_obj_path: ${{ inputs.auth_obj_path }} + auth_str: ${{ inputs.auth_str }} + dry_run: false - - name: get stackql command - uses: actions/github-script@v6 + - name: Check Results + uses: actions/github-script@v7.0.1 with: script: | const path = require('path'); - const utilsPath = path.join(process.env.GITHUB_ACTION_PATH, 'lib', 'utils.js') - const {getStackqlCommand} = require(utilsPath) - getStackqlCommand(core) - env: - QUERY_FILE_PATH: ${{ inputs.test_query_file_path }} - QUERY: ${{inputs.test_query}} - DATA_FILE_PATH: ${{inputs.data_file_path}} - VARS: ${{inputs.vars}} - OUTPUT: 'json' - - - name: execute stackql command - id: exec-query - shell: bash - run: | - ${{ env.STACKQL_COMMAND }} - - - name: Check results - uses: actions/github-script@v6 - with: - script: | - const path = require('path'); - const assertPath = path.join(process.env.GITHUB_ACTION_PATH, 'stackql-assert.js') + const assertPath = path.join(process.env.GITHUB_ACTION_PATH, 'lib', 'assert.js') const {assertResult} = require(assertPath) assertResult(core) env: - RESULT: ${{steps.exec-query.outputs.stdout}} + RESULT: ${{ steps.exec-query.outputs.stackql-query-results }} EXPECTED_RESULTS_STR: ${{ inputs.expected_results_str }} - EXPECTED_RESULTS_FILE_PATH: ${{inputs.expected_results_file_path}} - EXPECTED_ROWS: ${{inputs.expected_rows}} - + EXPECTED_RESULTS_FILE_PATH: ${{ inputs.expected_results_file_path }} + EXPECTED_ROWS: ${{ inputs.expected_rows }} - - branding: icon: 'terminal' - color: 'green' \ No newline at end of file + color: 'blue' diff --git a/lib/assert.js b/lib/assert.js index d1d1825..7c7332e 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -1,133 +1,75 @@ -let core; const fs = require("fs"); -const parseResult = (resultStr, varName) => { - const regex = /\[(.*)\]/; // match the first occurrence of square brackets and capture everything in between - const matches = resultStr.match(regex); - if (matches) { - const jsonStr = matches[0]; // extract the captured string - try { - const jsonObj = JSON.parse(jsonStr); // parse the JSON string into an object - return jsonObj; - } catch (error) { - throw(`Failed to parse ${varName} JSON - \nvalue: ${resultStr} - \nerror: ${error}`); - } +const parseResult = (resultStr, description = "result") => { + try { + return JSON.parse(resultStr); + } catch (error) { + throw new Error(`❌ Failed to parse ${description} JSON: + Value: ${resultStr} + Error: ${error.message}`); } - return null; // return null if no JSON object was found in the string }; const getExpectedResult = (expectedResultStr, expectedResultFilePath) => { - let expectedResult; if (expectedResultStr) { - expectedResult = parseResult(expectedResultStr, "expectedResultStr"); + return parseResult(expectedResultStr, "expected results string"); } else if (expectedResultFilePath) { - const fileContent = fs.readFileSync(expectedResultFilePath).toString(); - expectedResult = parseResult(fileContent, "expectedResultFilePath"); + const fileContent = fs.readFileSync(expectedResultFilePath, 'utf-8'); + return parseResult(fileContent, "expected results file"); } - - return expectedResult; + return null; }; -const checkResult = (expectedResult, expectedRows, actualResult) => { - let equality; - let message; - expectedRows = parseInt(expectedRows); - // if only passed expectedRows, check expectedRows - // if only passed expected result, only check expected result - // if both passed, check both - if (expectedRows) { - equality = actualResult.length === expectedRows; - if(equality) { - message = `============ StackQL Assert Successful (row count) ============ \n - Expected Number of Rows: ${expectedRows} \n - Actual Number of Rows: ${actualResult.length} \n - `; - } else { - message = `============ StackQL Assert Failed (row count) ============ \n - Expected Number of Rows: ${expectedRows} \n - Actual Number of Rows: ${actualResult.length} \n - Execution Result: ${JSON.stringify(actualResult)} \n - `; - } +const checkResult = (core, expected, actual, expectedRows) => { + const actualLength = actual.length; + + if (expectedRows && actualLength !== parseInt(expectedRows)) { + core.error(`Expected rows: ${expectedRows}, got: ${actualLength}`); + return false; } - if (expectedResult) { - equality = JSON.stringify(expectedResult) === JSON.stringify(actualResult); - if(equality) { - message = `============ StackQL Assert Successful (expected results) ============`; - } else { - message = `============ StackQL Assert Failed (expected results) ============ \n - Expected: ${JSON.stringify(expectedResult)}\n - Actual: ${JSON.stringify(actualResult)} - `; - } + if (expected && JSON.stringify(expected) !== JSON.stringify(actual)) { + core.error(`Expected results do not match actual results.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`); + return false; } - return { equality, message }; + return true; }; -function checkParameters(expectedResultStr, expectedResultFilePath, expectedRows) { - const params = [ - { name: "expectedResultStr", value: expectedResultStr }, - { name: "expectedResultFilePath", value: expectedResultFilePath }, - { name: "expectedRows", value: expectedRows }, - ]; +function assertResult(core) { + try { + const { RESULT, EXPECTED_RESULTS_STR, EXPECTED_RESULTS_FILE_PATH, EXPECTED_ROWS } = process.env; + core.info(`RESULT: ${RESULT}`); + core.info(`EXPECTED_RESULTS_STR: ${EXPECTED_RESULTS_STR}`); + core.info(`EXPECTED_RESULTS_FILE_PATH: ${EXPECTED_RESULTS_FILE_PATH}`); + core.info(`EXPECTED_ROWS: ${EXPECTED_ROWS}`); - const missingParams = params - .filter(param => !param.value || param.value === "undefined") - .map(param => param.name) - - if (missingParams.length === 3) { - const errorMessage = "Cannot find expected result, file path or expected rows"; - throw errorMessage; - } -} + if (!RESULT) throw new Error("Result from StackQL execution is missing."); + + // if no EXPECTED_RESULTS_STR, EXPECTED_RESULTS_FILE_PATH or EXPECTED_ROWS, fail the action + if (!EXPECTED_RESULTS_STR && !EXPECTED_RESULTS_FILE_PATH && !EXPECTED_ROWS) throw new Error("❌ Cannot find expected result, file path or expected rows"); + + const actualResult = parseResult(RESULT); + core.info("🔍 Checking results..."); -const assertResult = (coreObj) =>{ - core = coreObj; - try { - let [ - execResultStr, - expectedResultStr, - expectedResultFilePath, - expectedRows, - ] = [ - process.env.RESULT, - process.env.EXPECTED_RESULTS_STR, - process.env.EXPECTED_RESULTS_FILE_PATH, - process.env.EXPECTED_ROWS, - ]; - - checkParameters(expectedResultStr, expectedResultFilePath, expectedRows) - - let expectedResult = getExpectedResult( - expectedResultStr, - expectedResultFilePath - ); - - const actualResult = parseResult(execResultStr); - - if (!actualResult) { - core.setFailed("No Output from executing query"); - } - - const {equality, message} = checkResult(expectedResult, expectedRows, actualResult); - if (equality) { - core.info(message); - } else { - core.setFailed(message); - } - } catch (e) { - core.setFailed(e); + const expectedResult = getExpectedResult(EXPECTED_RESULTS_STR, EXPECTED_RESULTS_FILE_PATH); + + const resultSuccessful = checkResult(core, expectedResult, actualResult, EXPECTED_ROWS); + + if (resultSuccessful) { + core.info("✅ StackQL Assert Successful"); + } else { + core.setFailed("❌ StackQL Assert Failed"); } + } catch (error) { + core.setFailed(`Assertion error: ${error.message}`); + } } module.exports = { - assertResult, parseResult, + getExpectedResult, checkResult, - getExpectedResult + assertResult }; diff --git a/lib/tests/assert.test.js b/lib/tests/assert.test.js index d3d1956..0bd4ffb 100644 --- a/lib/tests/assert.test.js +++ b/lib/tests/assert.test.js @@ -2,85 +2,14 @@ const {checkResult, parseResult, assertResult, getExpectedResult} = require('../ describe('parseResult', ()=>{ - it('should parsedResult correctly when it starts with register', ()=>{ - const resultString = `google provider, version 'v23.01.00116' successfully installed \n - [{"name":"stackql-demo-001","status":"TERMINATED"}]` - - const expected = [{"name":"stackql-demo-001","status":"TERMINATED"}] - - const actual = parseResult(resultString); - expect(actual).toEqual(expected); - }) - - it('should parsedResult correctly when it is only the object', ()=>{ + it('should parsedResult correctly', ()=>{ const resultString = `[{"name":"stackql-demo-001","status":"TERMINATED"}]` - const expected = [{"name":"stackql-demo-001","status":"TERMINATED"}] - const actual = parseResult(resultString); expect(actual).toEqual(expected); }) }) -describe('checkResult', ()=>{ - let expectedResult; - let actualResult; - beforeEach(()=>{ - expectedResult= [{"name":"stackql-demo-001","status":"TERMINATED"}] - actualResult= [{"name":"stackql-demo-001","status":"TERMINATED"}] - - }) - it('should return equality false when the result object does not match', ()=>{ - actualResult = [{"name":"stackql-demo-001","status":"RUNNING"}] - - const {equality} = checkResult(expectedResult, undefined, actualResult); - - expect(equality).toEqual(false); - }) - - it('should return equality true when the result object does match', ()=>{ - - const {equality} = checkResult(expectedResult, undefined, actualResult); - - expect(equality).toEqual(true); - }) - - it('should return equality false when expected row is not matching', ()=>{ - - const expectedRows = 2; - - const {equality} = checkResult(undefined, expectedRows, actualResult); - - expect(equality).toEqual(false); - }) - - it('should return equality true when expected row is matching', ()=>{ - const expectedRows = 1; - - const {equality} = checkResult(undefined, expectedRows, actualResult); - - expect(equality).toEqual(true); - }) - - it('should return equality false when expected row is matching, but result object does not match', ()=>{ - const expectedRows = 1; - actualResult = [{"name":"stackql-demo-001","status":"RUNNING"}] - - const {equality} = checkResult(expectedResult, expectedRows, actualResult); - - expect(equality).toEqual(false); - }) - - it('should return equality true when expected row is matching, but result object matches', ()=>{ - const expectedRows = 1; - actualResult = [{"name":"stackql-demo-001","status":"TERMINATED"}] - - const {equality} = checkResult(expectedResult, expectedRows, actualResult); - - expect(equality).toEqual(true); - }) -}) - describe('getExpectedResult', ()=>{ it('should return expectedResult when expectedResultStr is passed', ()=>{ const expectedResultStr = `[{"name":"stackql-demo-001","status":"TERMINATED"}]` @@ -102,106 +31,160 @@ describe('getExpectedResult', ()=>{ }) - describe('assertResult integration test', ()=>{ - let coreObj; - - const ACTION_ENV = { - RESULT: `google provider, version 'v23.01.00116' successfully installed \n - [{"name":"stackql-demo-001","status":"TERMINATED"}]`, - EXPECTED_RESULTS_STR: `[{"name":"stackql-demo-001","status":"TERMINATED"}]`, - EXPECTED_RESULTS_FILE_PATH: 'test.json', - EXPECTED_ROWS: 1 +describe('checkResult', () => { + const core = { + info: jest.fn(), + error: jest.fn(), + setFailed: jest.fn() + }; + + beforeEach(() => { + core.info.mockClear(); + core.error.mockClear(); + core.setFailed.mockClear(); + }); + + it('should return false and log an error when the actual length does not match expected rows', () => { + const expectedRows = "3"; + const actualResult = [{}, {}, {}, {}]; // 4 items, should not match expectedRows + + const result = checkResult(core, undefined, actualResult, expectedRows); + + expect(result).toBe(false); + expect(core.error).toHaveBeenCalledWith(`Expected rows: ${expectedRows}, got: ${actualResult.length}`); + }); + + it('should return true when the actual length matches expected rows', () => { + const expectedRows = "2"; + const actualResult = [{}, {}]; // 2 items, matches expectedRows + + const result = checkResult(core, undefined, actualResult, expectedRows); + + expect(result).toBe(true); + expect(core.error).not.toHaveBeenCalled(); + }); + + it('should return false and log an error when expected does not match actual and expectedRows is undefined', () => { + const expected = [{ name: "test1" }, { name: "test2" }]; + const actual = [{ name: "test1" }, { name: "test3" }]; + + const result = checkResult(core, expected, actual, undefined); + + expect(result).toBe(false); + expect(core.error).toHaveBeenCalledWith(`Expected results do not match actual results.\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(actual)}`); + }); + + it('should return true when expected matches actual and expectedRows is undefined', () => { + const expected = [{ name: "test1" }, { name: "test2" }]; + const actual = [{ name: "test1" }, { name: "test2" }]; + + const result = checkResult(core, expected, actual, undefined); + + expect(result).toBe(true); + expect(core.error).not.toHaveBeenCalled(); + }); +}); + +describe('assertResult', ()=>{ + let coreObj; + + const ACTION_ENV = { + RESULT: `[{"name":"stackql-demo-001","status":"TERMINATED"}]`, + EXPECTED_RESULTS_STR: `[{"name":"stackql-demo-001","status":"TERMINATED"}]`, + EXPECTED_RESULTS_FILE_PATH: 'test.json', + EXPECTED_ROWS: 1 + } + + beforeEach(() => { + jest.resetModules() + process.env = {...ACTION_ENV} + coreObj = { + setFailed: jest.fn(), + info: jest.fn(), + error: jest.fn(), } - - beforeEach(() => { - jest.resetModules() - process.env = {...ACTION_ENV} - coreObj = { - setFailed: jest.fn(), - info: jest.fn().mockImplementation((message)=>{console.log(message)}) - } - }) - afterEach(() => { - process.env = ACTION_ENV - }) + }) + afterEach(() => { + process.env = ACTION_ENV + }) - it('it should setFailed when there is expected results are undefined', () => { - process.env.EXPECTED_RESULTS_FILE_PATH = undefined - process.env.EXPECTED_RESULTS_STR = undefined - process.env.EXPECTED_ROWS = undefined + it('it should setFailed when there is expected results are undefined', () => { + process.env.EXPECTED_RESULTS_FILE_PATH = undefined + process.env.EXPECTED_RESULTS_STR = undefined + process.env.EXPECTED_ROWS = undefined - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).toHaveBeenCalledWith('Cannot find expected result, file path or expected rows') - }); + expect(coreObj.setFailed).toHaveBeenCalledWith('Assertion error: ❌ Cannot find expected result, file path or expected rows') + }); - it('it should setFailed when actual result is not equal to expected result', () => { - process.env.RESULT= "[{\"name\":\"stackql-demo-001\",\"status\":\"RUNNING\"}]" + it('it should setFailed when actual result is not equal to expected result', () => { + process.env.RESULT= "[{\"name\":\"stackql-demo-001\",\"status\":\"RUNNING\"}]" - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) - }); + expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) + }); - it('it should not setFailed when actual result equal to expected result', () => { - assertResult(coreObj) + it('it should not setFailed when actual result equal to expected result', () => { + assertResult(coreObj) - expect(coreObj.setFailed).not.toHaveBeenCalled() - expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) - }); + expect(coreObj.setFailed).not.toHaveBeenCalled() + expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) + }); - it('it should setFailed when actual result is not equal to expected result', () => { - process.env.EXPECTED_RESULTS_STR= undefined; - process.env.EXPECTED_RESULTS_FILE_PATH = 'lib/tests/failed-result.json' + it('it should setFailed when actual result is not equal to expected result', () => { + process.env.EXPECTED_RESULTS_STR= undefined; + process.env.EXPECTED_RESULTS_FILE_PATH = 'lib/tests/failed-result.json' - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) - }); + expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) + }); - it('it should not setFailed when actual result equal to expected result in file', () => { - process.env.EXPECTED_RESULTS_STR= undefined; - process.env.EXPECTED_RESULTS_FILE_PATH = 'lib/tests/success-result.json' + it('it should not setFailed when actual result equal to expected result in file', () => { + process.env.EXPECTED_RESULTS_STR= undefined; + process.env.EXPECTED_RESULTS_FILE_PATH = 'lib/tests/success-result.json' - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).not.toHaveBeenCalled() - expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) - }) + expect(coreObj.setFailed).not.toHaveBeenCalled() + expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) + }) - it('it should setFailed when actual result does not match expected rows', () => { - process.env.EXPECTED_RESULTS_STR= undefined; - process.env.EXPECTED_RESULTS_FILE_PATH = undefined; - process.env.EXPECTED_ROWS = 2 + it('it should setFailed when actual result does not match expected rows', () => { + process.env.EXPECTED_RESULTS_STR= undefined; + process.env.EXPECTED_RESULTS_FILE_PATH = undefined; + process.env.EXPECTED_ROWS = 2 - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) - }); + expect(coreObj.setFailed).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Failed')) + }); - it('it should not setFailed when actual result match expected rows', ()=>{ - process.env.EXPECTED_RESULTS_STR= undefined; - process.env.EXPECTED_RESULTS_FILE_PATH = undefined; - process.env.EXPECTED_ROWS = 1 + it('it should not setFailed when actual result match expected rows', ()=>{ + process.env.EXPECTED_RESULTS_STR= undefined; + process.env.EXPECTED_RESULTS_FILE_PATH = undefined; + process.env.EXPECTED_ROWS = 1 - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).not.toHaveBeenCalled() - expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) - }) + expect(coreObj.setFailed).not.toHaveBeenCalled() + expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) + }) - it('it should not setFailed when actual result match expected rows in string number', ()=>{ - process.env.EXPECTED_RESULTS_STR= undefined; - process.env.EXPECTED_RESULTS_FILE_PATH = undefined; - process.env.EXPECTED_ROWS = '1' + it('it should not setFailed when actual result match expected rows in string number', ()=>{ + process.env.EXPECTED_RESULTS_STR= undefined; + process.env.EXPECTED_RESULTS_FILE_PATH = undefined; + process.env.EXPECTED_ROWS = '1' - assertResult(coreObj) + assertResult(coreObj) - expect(coreObj.setFailed).not.toHaveBeenCalled() - expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) - }) + expect(coreObj.setFailed).not.toHaveBeenCalled() + expect(coreObj.info).toHaveBeenCalledWith(expect.stringContaining('StackQL Assert Successful')) + }) - + - }) +}) diff --git a/lib/tests/test-auth.json b/lib/tests/test-auth.json deleted file mode 100644 index d8de066..0000000 --- a/lib/tests/test-auth.json +++ /dev/null @@ -1 +0,0 @@ -{ "google": { "type": "service_account", "credentialsfilepath": "sa-key.json" }} \ No newline at end of file diff --git a/lib/tests/utils.test.js b/lib/tests/utils.test.js deleted file mode 100644 index 4284027..0000000 --- a/lib/tests/utils.test.js +++ /dev/null @@ -1,123 +0,0 @@ -const { setupAuth, getStackqlCommand } = require("../utils"); - -describe("util", () => { - let core; - const expectedAuth = '{ "google": { "type": "service_account", "credentialsfilepath": "sa-key.json" }}'; - - beforeEach(() => { - core = { - setFailed: jest.fn(), - info: jest.fn(), - exportVariable: jest.fn(), - error: jest.fn(), - }; - }); - - describe("setupAuth", () => { - let AUTH_ENV = { - AUTH_STR: expectedAuth, - AUTH_FILE_PATH: "./lib/tests/test-auth.json", - }; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...AUTH_ENV }; - }); - - afterEach(() => { - process.env = AUTH_ENV; - }); - - it("should not throw an error when neither AUTH_STR or AUTH_FILE_PATH is set", () => { - process.env.AUTH_STR = undefined; - process.env.AUTH_FILE_PATH = undefined; - - setupAuth(core); - - expect(core.setFailed).not.toBeCalled(); - }); - - it("should set AUTH environment variable when AUTH_STR is set", () => { - process.env.AUTH_FILE_PATH = undefined; - - setupAuth(core); - - expect(core.exportVariable).toBeCalledWith("AUTH", expectedAuth); - }); - - it("should set AUTH environment variable when AUTH_FILE_PATH is set", () => { - process.env.AUTH_STR = undefined; - - setupAuth(core); - - expect(core.exportVariable).toBeCalledWith("AUTH", expectedAuth); - }); - - it("should throw error when AUTH_FILE_PATH is set but file does not exist", () => { - process.env.AUTH_STR = undefined; - process.env.AUTH_FILE_PATH = "./failed-test-auth.json"; - - setupAuth(core); - - expect(core.setFailed).toBeCalledWith(`Cannot find auth file ${process.env.AUTH_FILE_PATH}`); - }); - }); - - describe("getStackqlCommand", () => { - const EXECUTE_ENV = { - QUERY: "test", - QUERY_FILE_PATH: "test-query.iql", - AUTH: "test-auth", - }; - - beforeEach(() => { - jest.resetModules(); - process.env = { ...EXECUTE_ENV }; - }); - - afterEach(() => { - process.env = EXECUTE_ENV; - jest.clearAllMocks(); - }); - - it("should return error when there is neither query or query file path", () => { - process.env.QUERY = undefined; - process.env.QUERY_FILE_PATH = undefined; - - getStackqlCommand(core); - - expect(core.setFailed).toBeCalledWith("Either test_query or test_query_file_path need to be set"); - }); - - // it("should not return error when there is no AUTH", () => { - // process.env.AUTH = undefined; - - // getStackqlCommand(core); - - // expect(core.setFailed).not.toBeCalled(); - // expect(core.exportVariable).toBeCalledWith( - // "STACKQL_COMMAND", "stackql exec \"test\" --output='json'" - // ); - // }); - - it("should execute stackql with query file path", () => { - process.env.QUERY = undefined; - - getStackqlCommand(core); - - expect(core.exportVariable).toBeCalledWith( - "STACKQL_COMMAND", "stackql exec -i test-query.iql --auth='test-auth' --output='json'" - ); - }); - - it("should execute stackql with query", () => { - process.env.QUERY_FILE_PATH = undefined; - - getStackqlCommand(core); - - expect(core.exportVariable).toBeCalledWith( - "STACKQL_COMMAND", "stackql exec \"test\" --auth='test-auth' --output='json'" - ); - }); - }); -}); diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index c9c6990..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,91 +0,0 @@ -const fs = require("fs"); - -function setupAuth(core) { - let auth; - const fileName = process.env.AUTH_FILE_PATH; - const authStr = process.env.AUTH_STR; - - if (!checkEnvVarValid(fileName) && !checkEnvVarValid(authStr)) { - // core.info("Neither AUTH_FILE_PATH nor AUTH_STR is set. Proceeding using default provider environment variable names."); - return; - } - - if (checkEnvVarValid(fileName)) { - try { - // Read the contents of the JSON file into a string - auth = fs.readFileSync(fileName, "utf-8"); - } catch (error) { - core.error(error); - core.setFailed(`Cannot find auth file ${fileName}`); - return; - } - } - if (checkEnvVarValid(authStr)) { - auth = authStr - } - - core.info("Setting AUTH environment variable..."); - core.exportVariable("AUTH", auth); -} - -async function getStackqlCommand(core) { - - const [query, queryFilePath, dataFilePath, vars, auth, output = "json"] = [ - process.env.QUERY, - process.env.QUERY_FILE_PATH, - process.env.DATA_FILE_PATH, - process.env.VARS, - process.env.AUTH, - process.env.OUTPUT, - ]; - - if (!checkEnvVarValid(query) && !checkEnvVarValid(queryFilePath)) { - core.setFailed("Either test_query or test_query_file_path need to be set"); - return; - } - - let args = ["exec"]; - - if (query) { - args.push(`"${query}"`); - } else { - args.push("-i", queryFilePath); - } - - if (checkEnvVarValid(dataFilePath)) { - args.push(`--iqldata='${dataFilePath}'`); - } - - if (checkEnvVarValid(vars)) { - args.push(`--var='${vars}'`); - } - - if (checkEnvVarValid(auth)) { - args.push(`--auth='${auth}'`); - } - - args.push(`--output='${output}'`); - - try { - core.exportVariable('STACKQL_COMMAND', `stackql ${args.join(" ")}`) - } catch (error) { - core.error(error); - core.setFailed("Error when executing stackql"); - } -} - -/** - * Checking if environment variable is not empty or undefined - * @param {*} variable - */ -const checkEnvVarValid = (variable) => { - if (!variable || variable === "" || variable === "undefined") { - return false; - } - return true; -}; - -module.exports = { - setupAuth, - getStackqlCommand, -}; diff --git a/stackql-assert.js b/stackql-assert.js deleted file mode 100644 index 9793059..0000000 --- a/stackql-assert.js +++ /dev/null @@ -1,4 +0,0 @@ -const { assertResult } = require('./lib/assert') -module.exports ={ - assertResult -} \ No newline at end of file