diff --git a/README.md b/README.md index 30722e1..fcac6d6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple, cross platform, command line tool to test MSSQL procedures without the needs of a SSDT project or custom procedures / assemblies. Define your own YAML file with before / after scripts (inline or external text files), input parameters and you can verify: -- Result sets column names, column order, column types and row number +- Result sets column names, column types, row number and valiues - Output parameters type and value - Return code value - Custom asserts in the form of a SQL query returning a scalar value @@ -42,7 +42,7 @@ docker run --rm -t \ ``` ### 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. +This container contains ( ;-) ) Microsoft SQL Server 2022 *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 `/db` container folder - the YAML files: the folder containing them wil be mounted on the `/tests` container folder diff --git a/Taskfile_dist.yml b/Taskfile_dist.yml index 729f3a0..e8d0fde 100644 --- a/Taskfile_dist.yml +++ b/Taskfile_dist.yml @@ -6,7 +6,7 @@ tasks: build: desc: build project cmds: - - dotnet build ./src/qest/ + - dotnet build -c Release ./src/qest/ build:binaries: desc: build binaries deps: diff --git a/docs/yamlSchema.json b/docs/yamlSchema.json index 174f089..bf56264 100644 --- a/docs/yamlSchema.json +++ b/docs/yamlSchema.json @@ -1,97 +1,164 @@ { "$schema": "http://json-schema.org/draft-04/schema", - "definitions": { "Test": { "properties": { - "name": { "type": "string" }, + "name": { + "type": "string" + }, "variables": { "type": "object", "additionalProperties": true }, - "before": { "$ref": "#/definitions/Scripts" }, + "before": { + "$ref": "#/definitions/Scripts" + }, "steps": { "type": "array", - "items": { "$ref": "#/definitions/TestStep" } + "items": { + "$ref": "#/definitions/TestStep" + } }, - "after": { "$ref": "#/definitions/Scripts" } + "after": { + "$ref": "#/definitions/Scripts" + } }, - "required": ["name", "steps"], + "required": [ + "name", + "steps" + ], "additionalProperties": false }, "TestStep": { "properties": { - "name": { "type": "string" }, - "command": { "$ref": "#/definitions/TestCommand" }, - "results": { "$ref": "#/definitions/ResultGroup" }, + "name": { + "type": "string" + }, + "command": { + "$ref": "#/definitions/TestCommand" + }, + "results": { + "$ref": "#/definitions/ResultGroup" + }, "asserts": { "type": "array", - "items": { "$ref": "#/definitions/Assert" } + "items": { + "$ref": "#/definitions/Assert" + } } }, - "required": ["name", "command", "results"], + "required": [ + "name", + "command", + "results" + ], "additionalProperties": false }, "TestCommand": { "properties": { - "commandText": { "type": "string" }, - "parameters": { "additionalProperties": true } + "commandText": { + "type": "string" + }, + "parameters": { + "additionalProperties": true + } }, - "required": ["commandText"], + "required": [ + "commandText" + ], "additionalProperties": false }, "ResultGroup": { "properties": { "resultSets": { "type": "array", - "items": { "$ref": "#/definitions/ResultSet" } + "items": { + "$ref": "#/definitions/ResultSet" + } }, "outputParameters": { "type": "array", - "items": { "$ref": "#/definitions/OutputParameter" } + "items": { + "$ref": "#/definitions/OutputParameter" + } }, - "returnCode": { "type": "number" } + "returnCode": { + "type": "number" + } }, "additionalProperties": false }, "ResultSet": { "properties": { - "name": { "type": "string" }, - "rowNumber": { "type": "number" }, + "name": { + "type": "string" + }, + "rowNumber": { + "type": "number" + }, "columns": { "type": "array", "items": { "type": "object", "properties": { - "name": { "type": "string" }, - "type": { "$ref": "#/definitions/SqlDbType" } + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/SqlDbType" + } }, - "required": ["name", "type"], + "required": [ + "name", + "type" + ], "additionalProperties": false } + }, + "data": { + "$ref": "#/definitions/DataScript" } }, - "required": ["name", "columns"], + "required": [ + "name", + "columns" + ], "additionalProperties": false }, "OutputParameter": { "type": "object", "properties": { - "name": { "type": "string" }, - "type": { "$ref": "#/definitions/SqlDbType" }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/SqlDbType" + }, "value": {} }, - "required": ["name", "type", "value"], + "required": [ + "name", + "type", + "value" + ], "additionalProperties": false }, "Assert": { "type": "object", "properties": { - "sqlQuery": { "type": "string" }, - "scalarType": { "$ref": "#/definitions/SqlDbType" }, + "sqlQuery": { + "type": "string" + }, + "scalarType": { + "$ref": "#/definitions/SqlDbType" + }, "scalarValue": {} }, - "required": ["sqlQuery", "scalarType", "scalarValue"], + "required": [ + "sqlQuery", + "scalarType", + "scalarValue" + ], "additionalProperties": false }, "Scripts": { @@ -108,14 +175,44 @@ }, "values": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "DataScript": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/ScriptType" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "separator": { + "type": "string" + }, + "skipField": { + "type": "string" } }, + "required": [ + "type", + "values" + ], "additionalProperties": false }, "ScriptType": { "type": "string", - "enum": ["File", "Inline"] + "enum": [ + "File", + "Inline" + ] }, "SqlDbType": { "type": "string", @@ -138,10 +235,9 @@ ] } }, - "type": "array", "items": { "$ref": "#/definitions/Test" }, "additionalProperties": false -} +} \ No newline at end of file diff --git a/samples/sampleDb/dbo/Stored Procedures/SampleSP.sql b/samples/sampleDb/dbo/Stored Procedures/SampleSP.sql index aed5620..f581b11 100644 --- a/samples/sampleDb/dbo/Stored Procedures/SampleSP.sql +++ b/samples/sampleDb/dbo/Stored Procedures/SampleSP.sql @@ -43,7 +43,8 @@ BEGIN [DateTime2Value], [DateTimeOffsetValue], [DateValue], - [Time] + [Time], + NULL AS [NullValue] FROM dbo.SampleTable WHERE [Name] = @name diff --git a/samples/scripts/results.csv b/samples/scripts/results.csv new file mode 100644 index 0000000..b4683e2 --- /dev/null +++ b/samples/scripts/results.csv @@ -0,0 +1 @@ +{nameVar};True;*;7777;2;1210000000;3.14159265;1.1235813;21;0.00;1985-10-26 09:00:00.000;2015-10-21 07:28:00.0000000;1955-11-12 06:38:00.0000000 +00:00;1900-01-01;09:40:00; \ No newline at end of file diff --git a/samples/tests/sampleSp.yml b/samples/tests/sampleSp.yml index 5285bed..e2a6dd8 100644 --- a/samples/tests/sampleSp.yml +++ b/samples/tests/sampleSp.yml @@ -51,6 +51,12 @@ type: Date - name: TimeValue type: Time + - name: NullValue + type: Int + data: + type: Inline + values: + - "SampleName;True;42;*" outputParameters: - name: oldValue type: Int @@ -60,6 +66,64 @@ - sqlQuery: SELECT COUNT(*) FROM dbo.SampleTable WHERE [IntValue] = {newValueVar} scalarType: Int scalarValue: 1 + - name: Test OK with csv data + command: + commandText: dbo.SampleSp + parameters: + name: "{nameVar}" + newValue: 2 + 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 + - name: NullValue + type: Int + data: + type: File + values: + - scripts/results.csv + separator: ";" + skipField: "*" + outputParameters: + - name: oldValue + type: Int + value: 1 + returnCode: 0 + asserts: + - sqlQuery: SELECT COUNT(*) FROM dbo.SampleTable WHERE [IntValue] = 2 + scalarType: Int + scalarValue: 1 - name: Test KO command: commandText: dbo.SampleSp @@ -107,7 +171,7 @@ commandText: dbo.SampleSp parameters: name: "NoMatchData" - newValue: a + newValue: NULL results: returnCode: 0 after: diff --git a/src/qest.sln b/src/qest.sln index 4ead375..3aa7a19 100644 --- a/src/qest.sln +++ b/src/qest.sln @@ -5,6 +5,11 @@ VisualStudioVersion = 17.3.32922.545 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "qest", "qest\qest.csproj", "{79EF488B-B5FE-4DEE-807E-51CA359318C9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78937758-7B07-465D-A4CA-FE5DB8F29435}" + ProjectSection(SolutionItems) = preProject + ..\docs\yamlSchema.json = ..\docs\yamlSchema.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/qest/Connectors/MsSqlConnector.cs b/src/qest/Connectors/MsSqlConnector.cs index 41ba390..a37c4bc 100644 --- a/src/qest/Connectors/MsSqlConnector.cs +++ b/src/qest/Connectors/MsSqlConnector.cs @@ -219,7 +219,7 @@ private async Task RunScriptAsync(Scripts scripts) scripts.ActualScripts = new(); foreach (var item in scripts) { - foreach (string innerScript in item.GetValues()) + foreach (string innerScript in item.ReadValue()) { string actualScript = (string)innerScript.ReplaceVarsInParameter(currentTest.Variables); scripts.ActualScripts.Add(actualScript); diff --git a/src/qest/Models/Script.cs b/src/qest/Models/Script.cs index f4b0f0f..4de047b 100644 --- a/src/qest/Models/Script.cs +++ b/src/qest/Models/Script.cs @@ -6,12 +6,19 @@ namespace qest.Models { - public class Script + public class TextArray { public ScriptType Type { get; set; } public List Values { get; set; } - - public IEnumerable GetValues() + + /// + /// Read every item as a whole in the collection + /// + /// A single element of the array + /// + /// + /// + public IEnumerable ReadValue() { if (Values == null) throw new ArgumentNullException(nameof(Values)); @@ -23,6 +30,7 @@ public IEnumerable GetValues() case ScriptType.Inline: yield return value; break; + case ScriptType.File: if (File.Exists(value)) @@ -34,6 +42,49 @@ public IEnumerable GetValues() else throw new FileNotFoundException(null, value); break; + + default: + throw new ArgumentException(nameof(Type)); + } + } + + } + + /// + /// Reads every item in the collection line by line + /// + /// A single line of the collection + /// + /// + /// + public IEnumerable ReadLine() + { + if (Values == null) + throw new ArgumentNullException(nameof(Values)); + + foreach (var value in Values) + { + switch (this.Type) + { + case ScriptType.Inline: + yield return value; + break; + + case ScriptType.File: + + if (File.Exists(value)) + { + using var sr = new StreamReader(value); + while (sr.Peek() >= 0) + { + string data = sr.ReadLine(); + yield return data; + } + } + else + throw new FileNotFoundException(null, value); + break; + default: throw new ArgumentException(nameof(Type)); } @@ -48,7 +99,7 @@ public enum ScriptType File } - public class Scripts : List