From f7b3c5bdf95b4b7eedb383d8ba8769ed0288f0bb Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Tue, 16 Dec 2025 18:47:38 -0500 Subject: [PATCH] Added new properties to Test proto --- core/actions/test.ts | 43 ++++++++- core/actions/test_test.ts | 177 +++++++++++++++++++++++++++++++++++++- protos/core.proto | 10 +++ 3 files changed, 223 insertions(+), 7 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index b6d69d204..e3929e57e 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -1,8 +1,8 @@ import { verifyObjectMatchesProto, VerifyProtoErrorBehaviour } from "df/common/protos"; import { ActionBuilder, INamedConfig, TableType } from "df/core/actions"; import { IncrementalTable } from "df/core/actions/incremental_table"; -import { Table } from "df/core/actions/table"; -import { View } from "df/core/actions/view"; +import { Table, TableContext } from "df/core/actions/table"; +import { View, ViewContext } from "df/core/actions/view"; import { Contextable, IActionContext, ITableContext, Resolvable } from "df/core/contextables"; import { Session } from "df/core/session"; import { targetStringifier } from "df/core/targets"; @@ -77,7 +77,8 @@ export class Test extends ActionBuilder { /** @hidden We delay contextification until the final compile step, so hold these here for now. */ public contextableInputs = new Map>(); - private contextableQuery: Contextable; + private inputToTargets = new Map(); + private contextableQuery: Contextable; private datasetToTest: Resolvable; /** @@ -127,13 +128,20 @@ export class Test extends ActionBuilder { * Sets the input query to unit test against. */ public input(refName: string | string[], contextableQuery: Contextable) { + const target = resolvableAsTarget(toResolvable(refName)); + const inputName = targetStringifier.stringify(target); this.contextableInputs.set( - targetStringifier.stringify(resolvableAsTarget(toResolvable(refName))), + inputName, contextableQuery ); + this.inputToTargets.set(inputName, target); return this; } + public resolveSchema(resolveSchema: boolean) { + this.proto.resolveSchema = resolveSchema; + } + /** * Sets the expected output of the query to being tested against. */ @@ -188,7 +196,34 @@ export class Test extends ActionBuilder { } else { const refReplacingContext = new RefReplacingContext(testContext); this.proto.testQuery = refReplacingContext.apply(dataset.contextableQuery); + + // Set the test query with the fully qualified table references. + if (dataset instanceof Table) { + const tableContext = new TableContext(dataset); + this.proto.query = tableContext.apply(dataset.contextableQuery); + } else { + const viewContext = new ViewContext(dataset); + this.proto.query = viewContext.apply(dataset.contextableQuery); + } } + + // Build the inputs list with the fully qualified input names and the input query to mock each input. + this.contextableInputs.forEach((inputContextable, inputName) => { + const testInput = dataform.Test.TestInput.create(); + testInput.query = testContext.apply(inputContextable); + testInput.target = this.applySessionToTarget( + this.inputToTargets.get(inputName), + this.session.projectConfig, + this.getFileName(), + { + validateTarget: true + }); + this.proto.inputs.push(testInput); + }); + + // Add target property/ + this.proto.testTarget = dataset.getTarget(); + this.proto.resolveSchema = false; } this.proto.expectedOutputQuery = testContext.apply(this.contextableQuery); diff --git a/core/actions/test_test.ts b/core/actions/test_test.ts index 9ddccf712..75bb76ea9 100644 --- a/core/actions/test_test.ts +++ b/core/actions/test_test.ts @@ -1,5 +1,176 @@ -import { suite } from "df/testing"; +// tslint:disable tsr-detect-non-literal-fs-filename +import { expect } from "chai"; +import * as fs from "fs-extra"; +import * as path from "path"; -suite("test", () => { - // This action currently has no unit tests! +import { asPlainObject, suite, test } from "df/testing"; +import { TmpDirFixture } from "df/testing/fixtures"; +import { + coreExecutionRequestFromPath, + runMainInVm, + VALID_WORKFLOW_SETTINGS_YAML +} from "df/testing/run_core"; + +suite("test", ({ afterEach }) => { + const tmpDirFixture = new TmpDirFixture(afterEach); + + test(`test with no inputs`, () => { + const projectDir = tmpDirFixture.createNewTmpDir(); + const workflowSettingsPath = path.join(projectDir, "workflow_settings.yaml"); + const definitionsDir = path.join(projectDir, "definitions"); + const actionsYamlPath = path.join(definitionsDir, "actions.yaml"); + const actionSqlPath = path.join(definitionsDir, "action.sql"); + const actionTestSqlxPath = path.join(definitionsDir, "action_test.sqlx"); + + fs.writeFileSync(workflowSettingsPath, VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(definitionsDir); + fs.writeFileSync(actionsYamlPath, ` +actions: +- table: + filename: action.sql` + ); + fs.writeFileSync(actionSqlPath, "SELECT 1"); + fs.writeFileSync(actionTestSqlxPath, ` +config { + type: "test", + dataset: "action" +} +SELECT 1`); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + expect(asPlainObject(result.compile.compiledGraph.tests)).deep.equals( + asPlainObject([ + { + // Original test properties + name: "action_test", + testQuery: "SELECT 1", + expectedOutputQuery: "\n\nSELECT 1", + fileName: "definitions/action_test.sqlx", + + // New properties + testTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action" + }, + query: "SELECT 1", + resolveSchema: false, + } + ]) + ); + }); + + test(`test with multiple_inputs input`, () => { + const projectDir = tmpDirFixture.createNewTmpDir(); + const workflowSettingsPath = path.join(projectDir, "workflow_settings.yaml"); + const definitionsDir = path.join(projectDir, "definitions"); + const actionsYamlPath = path.join(definitionsDir, "actions.yaml"); + const action1SqlxPath = path.join(definitionsDir, "action1.sqlx"); + const action1TestSqlxPath = path.join(definitionsDir, "action1_test.sqlx"); + const action2SqlxPath = path.join(definitionsDir, "action2.sqlx"); + const action2TestSqlxPath = path.join(definitionsDir, "action2_test.sqlx"); + + fs.writeFileSync(workflowSettingsPath, VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(definitionsDir); + + // Add a declaration + fs.writeFileSync(actionsYamlPath, ` +actions: +- declaration: + name: a_declaration` + ); + + // Add an action with a test, reads from declaration + fs.writeFileSync(action1SqlxPath, ` +config { + type: "table", +} +SELECT a,b,c FROM \${ref("a_declaration")} + `); + fs.writeFileSync(action1TestSqlxPath, ` +config { + type: "test", + dataset: "action1" +} +input "a_declaration" { + SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d +} +SELECT 1 AS a, 2 AS b, 3 AS c`); + + + // Add an action with a test, reads from previous action + fs.writeFileSync(action2SqlxPath, ` +config { + type: "table", +} +SELECT a,b FROM \${ref("action1")} + `); + fs.writeFileSync(action2TestSqlxPath, ` +config { + type: "test", + dataset: "action2" +} +input "action1" { + SELECT 1 AS a, 2 AS b, 3 AS c +} +SELECT 1 AS a, 2 AS b`); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + expect(asPlainObject(result.compile.compiledGraph.tests)).deep.equals( + asPlainObject([ + { + // Original test properties + name: "action1_test", + testQuery: "\n\nSELECT a,b,c FROM (\n SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d\n)\n ", + expectedOutputQuery: "\n\n\nSELECT 1 AS a, 2 AS b, 3 AS c", + fileName: "definitions/action1_test.sqlx", + + // New properties + testTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action1" + }, + inputs: [{ + query: "\n SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d\n", + target: { + database: "defaultProject", + schema: "defaultDataset", + name: "a_declaration" + } + }], + query: "\n\nSELECT a,b,c FROM `defaultProject.defaultDataset.a_declaration`\n ", + resolveSchema: false, + }, + { + // Original test properties + name: "action2_test", + testQuery: "\n\nSELECT a,b FROM (\n SELECT 1 AS a, 2 AS b, 3 AS c\n)\n ", + expectedOutputQuery: "\n\n\nSELECT 1 AS a, 2 AS b", + fileName: "definitions/action2_test.sqlx", + + // New properties + testTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action2" + }, + inputs: [{ + query: "\n SELECT 1 AS a, 2 AS b, 3 AS c\n", + target: { + database: "defaultProject", + schema: "defaultDataset", + name: "action1" + } + }], + query: "\n\nSELECT a,b FROM `defaultProject.defaultDataset.action1`\n ", + resolveSchema: false, + } + ]) + ); + }); }); diff --git a/protos/core.proto b/protos/core.proto index d327960e8..1f6935519 100644 --- a/protos/core.proto +++ b/protos/core.proto @@ -264,6 +264,16 @@ message Test { // Generated. string file_name = 4; + + Target test_target = 5; + repeated TestInput inputs = 6; + string query = 7; + bool resolve_schema = 8; + + message TestInput { + Target target = 1; + string query = 2; + } } message Notebook {