diff --git a/.dockerignore b/.dockerignore
index c00861a..22818be 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,4 +1,5 @@
-DockerFile
+bundle.dockerfile
+standalone.dockerfile
.gitattributes
.gitignore
Readme.md
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1734be4..3260695 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,8 +6,8 @@ on:
tags:
- '*'
-jobs:
- build:
+jobs:
+ build-binaries:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -33,3 +33,35 @@ jobs:
artifacts: "*.gz"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
+
+ build-docker-standalone:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ name: Check out code
+
+ - uses: mr-smithers-excellent/docker-build-push@v5
+ name: Build & push Docker standalone image
+ with:
+ image: quest
+ tags: standalone-latest, standalone-${{ github.ref_name }}
+ registry: ghcr.io
+ dockerfile: standalone.dockerfile
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+
+ build-docker-bundle:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ name: Check out code
+
+ - uses: mr-smithers-excellent/docker-build-push@v5
+ name: Build & push Docker bundle image
+ with:
+ image: quest
+ tags: bundle-latest, bundle-${{ github.ref_name }}
+ registry: ghcr.io
+ dockerfile: bundle.dockerfile
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
\ No newline at end of file
diff --git a/README.md b/README.md
index ed53dbd..09828fb 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
-# qest
-
## What is qest?
A simple, cross platform, command line tool to test MSSQL procedures without the needs of a SSDT project or custom procedures / assemblies.
@@ -13,35 +11,55 @@ The tool does not implement a transaction logic for the tests: you have to provi
This is by design, to not interfere with the transaction logic that may be implemented in the stored procedures.
## Quickstart
-### Local
+### Local binary
Run tests in a single file:
```
-./qest --file relative/path/to/file.yml --tcs targetDatabaseConnectionString
+./qest run --file relative/path/to/file.yml --tcs targetDatabaseConnectionString
```
Run all tests in a folder:
```
-./qest --folder relative/path/to/folder --tcs targetDatabaseConnectionString
+./qest run --folder relative/path/to/folder --tcs targetDatabaseConnectionString
+```
+Generate templates from the database into an output directory:
+```
+./qest run --folder relative/path/to/folder --tcs targetDatabaseConnectionString
+```
+### Local container, provided database server
+Same options as the local binary version, but with a provided runtime.
```
-### Container
+docker run --rm -t \
+ -v {full/local/path/to/test/folder}:/tests \
+ -v {full/local/path/to/scripts/folder}:/scripts \
+ qest:standalone \
+ run --folder tests --tcs targetDatabaseConnectionString
+```
+or:
+```
+docker run --rm -t \
+ -v {full/local/path/to/template/folder}:/templates \
+ qest:standalone \
+ generate --folder templates --tcs targetDatabaseConnectionString
+```
+
+### Bundle container
+This container contains ( ;-) ) Microsoft SQL Server 2019 *and* qest executables, so you can deploy the database and run tests in a pristine environment.
You need to provide:
-- the `dacpac` file of your database: the folder containing it wil be mounted on the `/quest/db` container folder
-- the YAML files: the folder containing them wil be mounted on the `/quest/tests` container folder
-- the scripts: the folder containing them wil be mounted on the `/quest/scripts` container folder
+- the `dacpac` file of your database: the folder containing it wil be mounted on the `/db` container folder
+- the YAML files: the folder containing them wil be mounted on the `/tests` container folder
+- the scripts: the folder containing them wil be mounted on the `/scripts` container folder
-Please note: for this default image to work, YAML files have to reference the _File_ scripts in the `scripts/{filename}` form. See [docs](docs/YamlFormat.md#script).
+Please note: for this default image to work, YAML files have to reference the _File_ scripts in the `scripts/{filename}` form. See [docs](https://github.com/Geims83/qest/wiki/YamlFormat).
Run the image binding the `tests`, `scripts` and `db` directories and providing the correct environment variables:
```
-docker run --rm \
- -v {full/local/path/to/test/folder}:/qest/tests \
- -v {full/local/path/to/scripts/folder}:/qest/scripts \
- -v {full/local/path/to/dacpac/folder}:/qest/db \
+docker run --rm -t \
+ -v {full/local/path/to/test/folder}:/tests \
+ -v {full/local/path/to/scripts/folder}:/scripts \
+ -v {full/local/path/to/dacpac/folder}:/db \
--env DACPAC={filenameWithoutExtension} \
- ghcr.io/geims83/qest:latest
+ qest:bundle
```
-## Samples
-See [samples folder](samples/README.md).
-## YAML test definition
-See [docs](docs/YamlFormat.md).
\ No newline at end of file
+## Got _qest_ ...ions?
+Go to the [wiki](https://github.com/Geims83/qest/wiki)!
\ No newline at end of file
diff --git a/Dockerfile b/bundle.dockerfile
similarity index 54%
rename from Dockerfile
rename to bundle.dockerfile
index 6a02e5f..cd07f7c 100644
--- a/Dockerfile
+++ b/bundle.dockerfile
@@ -1,7 +1,7 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0 as build
COPY ./src ./src
-RUN dotnet publish /src/qest/ -o /qest --runtime linux-x64 -c Release --self-contained true /p:PublishSingleFile=true /p:PublishTrimmed=true
+RUN dotnet publish /src/qest/ -o /output --runtime linux-x64 -c Release --self-contained true /p:PublishSingleFile=true /p:PublishTrimmed=true
FROM mcr.microsoft.com/mssql/server:2019-latest AS mssql
USER root
@@ -9,12 +9,9 @@ USER root
# env vars needed by the mssql image
ENV ACCEPT_EULA Y
ENV SA_PASSWORD qestDbSecurePassword27!
-#
-#ENV DACPAC
-
-WORKDIR /qest
-COPY --from=build /qest/qest .
+WORKDIR /app
+COPY --from=build /output/ .
RUN chmod a+x qest
@@ -22,12 +19,16 @@ RUN chmod a+x qest
RUN apt-get update \
&& apt-get install unzip -y
-RUN wget -O sqlpackage.zip https://go.microsoft.com/fwlink/?linkid=2143497
+RUN wget -O sqlpackage.zip https://aka.ms/sqlpackage-linux
RUN unzip sqlpackage.zip
RUN chmod a+x sqlpackage
+WORKDIR /
+
# Launch SQL Server, confirm startup is complete, deploy the DACPAC, run tests.
# See https://stackoverflow.com/a/51589787/488695
+
ENTRYPOINT ["sh", "-c", "( /opt/mssql/bin/sqlservr & ) | grep -q \"Service Broker manager has started\" \
- && ./sqlpackage /a:Publish /sf:db/${DACPAC}.dacpac /tsn:. /tdn:$DACPAC /tu:sa /tp:$SA_PASSWORD \
- && ./qest --folder tests --tcs \"Server=localhost,1433;Initial Catalog=${DACPAC};User Id=sa;Password=${SA_PASSWORD}\""]
\ No newline at end of file
+ && PATH='$PATH':/app \
+ && sqlpackage /a:Publish /sf:db/${DACPAC}.dacpac /tsn:. /tdn:$DACPAC /tu:sa /tp:$SA_PASSWORD \
+ && qest run --folder tests --tcs \"Server=localhost,1433;Initial Catalog=${DACPAC};User Id=sa;Password=${SA_PASSWORD}\""]
\ No newline at end of file
diff --git a/docs/YamlFormat.md b/docs/YamlFormat.md
deleted file mode 100644
index 187eee6..0000000
--- a/docs/YamlFormat.md
+++ /dev/null
@@ -1,154 +0,0 @@
-# YAML test definition
-
-## Full Definition
-```
-- name:
- before:
- - type:
- values:
- -
- command:
- commandText:
- parameters:
- - name:
- type:
- value:
- results:
- resultSets:
- - name:
- rowNumber:
- columns:
- - name:
- type:
- outputParameters:
- - name:
- type:
- value:
- returnCode:
- asserts:
- - sqlQuery:
- scalarType:
- scalarValue:
- after:
- - type:
- values:
- -
-```
-## Sections
-### name
-_Required_
-The full name of the test.
-
-### before
-_Optional_
-Array of [Scripts](#script) object that runs before the command is executed.
-Before scripts run in a single transaction.
-
-### command
-__Mandatory__
-A single element representing the command (Stored Procedure) to run.
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__commandText__|The Stored Procedure to run|__M__||
-|__parameters__|A list of [Parameters](#parameter)|_O_||
-
-### results
-_Optional_
-The definition of the results to verify.
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|resultSets|A list of [ResultSets](#resultset)|_O_||
-|outputParameters|A list of [Parameters](#parameter)|_O_||
-|returnCode| The return code value |_O_||
-
-### asserts
-_Optional_
-The definition of the asserts to run.
-A list of [Assert](#assert) objects.
-
-### after
-_Optional_
-Array of [Scripts](#script) object that runs after the command is executed.
-Generally used to delete the data provided/generated by the test.
-
-## Types
-
-### Assert
-Definition of an assert.
-```
-sqlQuery:
-scalarType:
-scalarValue:
-```
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__sqlQuery__|Query to run|__M__|Must return a single value as a result set (one row, one column)|
-|__scalarType__|Expected type of the scalar returned from the assert|__M__|See [Types](#type)|
-|__scalarValue__|Expected value the scalar returned from the assert|__M__||
-
-### Parameter
-Definition of a parameter.
-```
-name:
-type:
-value:
-```
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__name__|Name of the parameter|__M__|Without __@__|
-|__type__|Type of the parameter|__M__|See [Types](#type)|
-|__value__|Value of the parameter|_O_|Mandatory depends on what is defined in the database|
-
-### ResultSet
-Definition of a result set.
-```
-- name:
- rowNumber:
- columns:
- - name:
- type:
-```
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__name__|Name of the result set|__M__||
-|__columns__|Definition of the columns of the result set|__M__||
-|__rowNumber__|Expected number of rows|_O_||
-
-Each column is defined as:
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__name__|Name of the column of the result set|__M__||
-|__type__|Type of the column|See [Types](#type)|__M__||
-
-### Script
-Definition of a script to run.
-```
-type: Inline | File
-values:
- -
-```
-|Attribute|Description|M/O|Notes|
-|---|---|:---:|---|
-|__type__|Type of the script|_Inline_ or _File_|__M__|
-|__value__|If __type__ is _Inline_: a list of SQL commands
If __type__ is _File_: a list of path to text files |__M__|Files path shoud be relative to the __qest__ excutable|
-
-Each __Script__ entity is ran atomically: the strings / files are concatenated in a single SQL batch joined by ";".
-
-### Type
-The types used are the text representation of the _SqlDBType_ C# enum.
-The supported types are:
- - Bit
- - TinyInt
- - SmallInt
- - BigInt
- - Int
- - Float
- - Real
- - Decimal
- - Money
- - NVarChar
- - DateTime
- - DateTime2
- - DateTimeOffset
- - Date
- - Time
\ No newline at end of file
diff --git a/samples/Dockerfile b/samples/Dockerfile
index f5db1f2..5ed5116 100644
--- a/samples/Dockerfile
+++ b/samples/Dockerfile
@@ -4,7 +4,7 @@ WORKDIR /build
COPY ./sampleDb .
RUN dotnet build . -c Release -o /dacpac
-FROM ghcr.io/geims83/qest:latest
+FROM qest:bundle
ENV DACPAC=sampleDb
diff --git a/samples/README.md b/samples/README.md
deleted file mode 100644
index de46866..0000000
--- a/samples/README.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# How to run the provided samples
-
-## Step 1 - Download this folder
-Clone or download this repo.
-
-## Step 2
-### Build your local image...
-Open a terminal in the folder where you have downloaded the data. Then run:
-```
-docker build . -t qestsamples
-docker run -t qestsamples
-```
-### ...or run directly from the registry
-Build the sample database:
-```
-dotnet build ./sampleDb/ -c Release -o ./dacpac
-```
-
-Then run the command:
-```
-docker run --rm -t \
- -v {full/local/path}/tests:/qest/tests \
- -v {full/local/path}/scripts:/qest/scripts \
- -v {full/local/path}/dacpac:/qest/db \
- --env DACPAC={filename} \
- ghcr.io/geims83/qest:latest
-```
-
-## Step 3: you're done!
-The output should look like this:
-```
-Running Test: SampleSP - Ok
-Running Before scripts...
-Completed.
-Checking ResultSet: sampleSpRS1
-Result sampleSpRS1: OK
-Checking Output Parameter: oldValue
-Result oldValue: 0 == 0
-Checking Return Code
-Return Code: 0 == 0
-Assert SELECT COUNT(*) FROM dbo.SampleTable WHERE [Value] = 1: 1 == 1
-Running After scripts...
-Completed.
-Test SampleSP - Ok: OK
-```
-
-You have run a couple of tests on the stored procedure, and everything looks fine and green!
-
-# ...Wait a minute!
-Ok, let's assume you want to be sure that everything is actually being tested - let's go brake things.
-Go to [the stored procedure definition](sampleDb/dbo/Stored%20Procedures/SampleSP.sql) and change a little bit of logic: let's say that we want to know how many rows are updated during the operation.
-
-At line 9, start by declaring **@rc** as 0:
-```
-DECLARE @rc TINYINT = 0:
-```
-
-And at line 29:
-```
-SET @rc = @@ROWCOUNT
-```
-
-Now build the image again: you should get an error, and a log like:
-```
-Running Test: SampleSP - Ok
-Running Before scripts...
-Completed.
-Checking ResultSet: sampleSpRS1
-Result sampleSpRS1: OK
-Checking Output Parameter: oldValue
-Result oldValue: 0 == 0
-Checking Return Code
-Return Code: 1 != 0
-Assert SELECT COUNT(*) FROM dbo.SampleTable WHERE [IntValue] = 1: 1 == 1
-Running After scripts...
-Completed.
-Test SampleSP - Ok: KO
-```
-As you see, the first test checked the resultset - good, the output parameter - good , but the return code expected was **0** and we got a **1**.
-
-Now: let's go to the [test definition](tests/sampleSp.yml).
-As we expect to update one row when we execute the procedures with these parameters, we have to change row 28 from **0** to **1**.
-But wait! We have another test in the definition!
-We have to change row 52 too, from **1** to **0** (in the negative test, we don't load the row we are trying to update, so @@ROWCOUNT is expected to be 0).
-
-Now build / run the image again and everything runs smoothly as previously.
\ No newline at end of file
diff --git a/samples/tests/sampleSp.yml b/samples/tests/sampleSp.yml
index a1c5847..20d0de7 100644
--- a/samples/tests/sampleSp.yml
+++ b/samples/tests/sampleSp.yml
@@ -1,112 +1,105 @@
-- name: SampleSP - Ok
+- name: SampleSP
+ variables:
+ nameVar: SampleName
+ newValueVar: 1
before:
- - type: File
- values:
- - scripts/SampleData.sql
- command:
- commandText: dbo.SampleSp
- parameters:
- - name: name
- type: NVarChar
- value: SampleName
- - name: newValue
- type: Int
- value: 1
- results:
- resultSets:
- - name: sampleSpRS1
- rowNumber: 1
- columns:
- - name: Name
- type: NVarChar
- - name: BitValue
- type: Bit
- - name: TinyIntValue
- type: TinyInt
- - name: SmallintValue
- type: Smallint
- - name: IntValue
- type: Int
- - name: BigIntValue
- type: BigInt
- - name: FloatValue
- type: Float
- - name: RealtValue
- type: Real
- - name: DecimalValue
- type: Decimal
- - name: MoneyValue
- type: Money
- - name: DateTimeValue
- type: DateTime
- - name: DateTime2Value
- type: DateTime2
- - name: DateTimeOffsetValue
- type: DateTimeOffset
- - name: DateValue
- type: Date
- - name: TimeValue
- type: Time
- outputParameters:
- - name: oldValue
- type: Int
- value: 0
- returnCode: 0
- asserts:
- - sqlQuery: SELECT COUNT(*) FROM dbo.SampleTable WHERE [IntValue] = 1
- scalarType: Int
- scalarValue: 1
+ - type: File
+ values:
+ - scripts/SampleData.sql
+ steps:
+ - name: Test OK
+ command:
+ commandText: dbo.SampleSp
+ parameters:
+ name: "{nameVar}"
+ newValue: "{newValueVar}"
+ results:
+ resultSets:
+ - name: sampleSpRS1
+ rowNumber: 1
+ columns:
+ - name: Name
+ type: NVarChar
+ - name: BitValue
+ type: Bit
+ - name: TinyIntValue
+ type: TinyInt
+ - name: SmallintValue
+ type: Smallint
+ - name: IntValue
+ type: Int
+ - name: BigIntValue
+ type: BigInt
+ - name: FloatValue
+ type: Float
+ - name: RealtValue
+ type: Real
+ - name: DecimalValue
+ type: Decimal
+ - name: MoneyValue
+ type: Money
+ - name: DateTimeValue
+ type: DateTime
+ - name: DateTime2Value
+ type: DateTime2
+ - name: DateTimeOffsetValue
+ type: DateTimeOffset
+ - name: DateValue
+ type: Date
+ - name: TimeValue
+ type: Time
+ outputParameters:
+ - name: oldValue
+ type: Int
+ value: 0
+ returnCode: 0
+ asserts:
+ - sqlQuery: SELECT COUNT(*) FROM dbo.SampleTable WHERE [IntValue] = {newValueVar}
+ scalarType: Int
+ scalarValue: 1
+ - name: Test KO
+ command:
+ commandText: dbo.SampleSp
+ parameters:
+ name: "NoMatchData"
+ newValue: "{newValueVar}"
+ results:
+ resultSets:
+ - name: sampleSpRS1
+ rowNumber: 0
+ columns:
+ - name: Name
+ type: NVarChar
+ - name: BitValue
+ type: Bit
+ - name: TinyIntValue
+ type: TinyInt
+ - name: SmallintValue
+ type: Smallint
+ - name: IntValue
+ type: Int
+ - name: BigIntValue
+ type: BigInt
+ - name: FloatValue
+ type: Float
+ - name: RealtValue
+ type: Real
+ - name: DecimalValue
+ type: Decimal
+ - name: MoneyValue
+ type: Money
+ - name: DateTimeValue
+ type: DateTime
+ - name: DateTime2Value
+ type: DateTime2
+ - name: DateTimeOffsetValue
+ type: DateTimeOffset
+ - name: DateValue
+ type: Date
+ - name: TimeValue
+ type: Time
+ returnCode: 1
after:
- - type: File
- values:
- - scripts/DeleteTestData.sql
-- name: SampleSP - KO
- command:
- commandText: dbo.SampleSp
- parameters:
- - name: name
- type: NVarChar
- value: NoMatchData
- - name: newValue
- type: Int
- value: 1
- results:
- resultSets:
- - name: sampleSpRS1
- rowNumber: 0
- columns:
- - name: Name
- type: NVarChar
- - name: BitValue
- type: Bit
- - name: TinyIntValue
- type: TinyInt
- - name: SmallintValue
- type: Smallint
- - name: IntValue
- type: Int
- - name: BigIntValue
- type: BigInt
- - name: FloatValue
- type: Float
- - name: RealtValue
- type: Real
- - name: DecimalValue
- type: Decimal
- - name: MoneyValue
- type: Money
- - name: DateTimeValue
- type: DateTime
- - name: DateTime2Value
- type: DateTime2
- - name: DateTimeOffsetValue
- type: DateTimeOffset
- - name: DateValue
- type: Date
- - name: TimeValue
- type: Time
- returnCode: 1
- after:
- - type: File
- values:
- - scripts/DeleteTestData.sql
\ No newline at end of file
+ - type: File
+ values:
+ - scripts/DeleteTestData.sql
diff --git a/src/TestBase/Script.cs b/src/TestBase/Script.cs
index ea0d873..4b9182c 100644
--- a/src/TestBase/Script.cs
+++ b/src/TestBase/Script.cs
@@ -5,7 +5,7 @@ public class Script
public ScriptType Type { get; set; }
public List Values { get; set; }
- public string Compact()
+ public string Compact(Dictionary? variables)
{
if (Values == null)
throw new ArgumentNullException(nameof(Values));
@@ -13,7 +13,7 @@ public string Compact()
switch (this.Type)
{
case ScriptType.Inline:
- return string.Join(";", Values);
+ return string.Join(";", Values).ReplaceVars(variables);
case ScriptType.File:
List list = new List();
@@ -28,7 +28,7 @@ public string Compact()
else
throw new FileNotFoundException(null, item);
}
- return string.Join(";", list);
+ return string.Join(";", list).ReplaceVars(variables);
default:
throw new ArgumentException(nameof(Type));
}
diff --git a/src/TestBase/Scripts.cs b/src/TestBase/Scripts.cs
new file mode 100644
index 0000000..d1f7ff2
--- /dev/null
+++ b/src/TestBase/Scripts.cs
@@ -0,0 +1,33 @@
+using System.Data.SqlClient;
+
+namespace TestBase
+{
+ public class Scripts : List