mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
* Make nullable warnings a per file opt-in * Remove unneeded compiler directives * Remove compiler directive for User Data
430 lines
17 KiB
C#
430 lines
17 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
|
using NUnit.Framework;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
|
{
|
|
public class SaveAsCsvFileStreamWriterTests
|
|
{
|
|
[Test]
|
|
public void Constructor_NullStream()
|
|
{
|
|
// Act
|
|
TestDelegate action = () => _ = new SaveAsCsvFileStreamWriter(
|
|
null,
|
|
new SaveResultsAsCsvRequestParams(),
|
|
Array.Empty<DbColumnWrapper>()
|
|
);
|
|
|
|
// Assert
|
|
Assert.Throws<ArgumentNullException>(action);
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_NullColumns()
|
|
{
|
|
// Act
|
|
TestDelegate action = () => _ = new SaveAsCsvFileStreamWriter(
|
|
Stream.Null,
|
|
new SaveResultsAsCsvRequestParams(),
|
|
null
|
|
);
|
|
|
|
// Assert
|
|
Assert.Throws<ArgumentNullException>(action);
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_WithoutSelectionWithHeader_WritesHeaderWithAllColumns()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has no selection made, headers should be printed
|
|
// ... Create a set of columns
|
|
// --- Create a memory location to store the output
|
|
var requestParams = new SaveResultsAsCsvRequestParams { IncludeHeaders = true };
|
|
var (columns, _) = GetTestValues(2);
|
|
using var outputStream = new MemoryStream();
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I construct a CSV file writer
|
|
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
|
writer.Dispose();
|
|
|
|
// Then:
|
|
// ... It should have written a line
|
|
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.AreEqual(1, lines.Length);
|
|
|
|
// ... It should have written a header line with two comma separated names
|
|
string[] headerValues = lines[0].Split(",");
|
|
Assert.AreEqual(2, headerValues.Length);
|
|
for (int i = 0; i < columns.Length; i++)
|
|
{
|
|
Assert.AreEqual(columns[i].ColumnName, headerValues[i]);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_WithSelectionWithHeader_WritesHeaderWithSelectedColumns()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has no selection made, headers should be printed
|
|
// ... Create a set of columns
|
|
// --- Create a memory location to store the output
|
|
var requestParams = new SaveResultsAsCsvRequestParams
|
|
{
|
|
IncludeHeaders = true,
|
|
ColumnStartIndex = 1,
|
|
ColumnEndIndex = 2,
|
|
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
|
RowEndIndex = 10
|
|
};
|
|
var (columns, _) = GetTestValues(4);
|
|
using var outputStream = new MemoryStream();
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I construct a CSV file writer
|
|
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
|
writer.Dispose();
|
|
|
|
// Then:
|
|
// ... It should have written a line
|
|
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.AreEqual(1, lines.Length);
|
|
|
|
// ... It should have written a header line with two comma separated names
|
|
string[] headerValues = lines[0].Split(",");
|
|
Assert.AreEqual(2, headerValues.Length);
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
Assert.AreEqual(columns[i + 1].ColumnName, headerValues[i]);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Constructor_WithoutSelectionWithoutHeader_DoesNotWriteHeader()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has no selection made, headers should not be printed
|
|
// ... Create a set of columns
|
|
// --- Create a memory location to store the output
|
|
var requestParams = new SaveResultsAsCsvRequestParams { IncludeHeaders = false };
|
|
var (columns, _) = GetTestValues(2);
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I construct a CSV file writer
|
|
using var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
|
writer.Dispose();
|
|
|
|
// Then:
|
|
// ... It not have written anything
|
|
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.IsEmpty(lines);
|
|
}
|
|
|
|
[TestCase("Something\rElse")] // Contains carriage return
|
|
[TestCase("Something\nElse")] // Contains line feed
|
|
[TestCase("Something\"Else")] // Contains default text identifier
|
|
[TestCase("Something,Else")] // Contains field separator
|
|
public void EncodeCsvField_ContainsDefaultControlCharacters_ShouldBeWrapped(string field)
|
|
{
|
|
// Setup: Create CsvFileStreamWriter using default control characters
|
|
using var writer = GetWriterForEncodingTests(null, null, null);
|
|
|
|
// If: I CSV encode a field that has forbidden characters in it
|
|
string output = writer.EncodeCsvField(field);
|
|
|
|
// Then: It should wrap it in quotes
|
|
Assert.True(Regex.IsMatch(output, "^\".*\"$", RegexOptions.Singleline));
|
|
}
|
|
|
|
[TestCase("Something\rElse")] // Contains carriage return [TODO: Don't support this]
|
|
[TestCase("Something\nElse")] // Contains line feed [TODO: Don't support this]
|
|
[TestCase("Something[Else")] // Contains default text identifier
|
|
[TestCase("Something$Else")] // Contains field separator
|
|
//[TestCase("Something||Else")] // Contains line break [TODO: Support this]
|
|
public void EncodeCsvField_ContainsNonDefaultControlCharacters_ShouldBeWrapped(string field)
|
|
{
|
|
// Setup: Create CsvFileStreamWriter using non-default control characters
|
|
var writer = GetWriterForEncodingTests("$foo", "[bar", "||");
|
|
|
|
// If: I CSV encode a field that has forbidden characters in it
|
|
string output = writer.EncodeCsvField(field);
|
|
|
|
// Then: It should wrap it in quotes
|
|
Assert.True(Regex.IsMatch(output, @"^\[.*\[$", RegexOptions.Singleline));
|
|
}
|
|
|
|
[TestCase("\tSomething")] // Starts with tab
|
|
[TestCase("Something\t")] // Ends with tab
|
|
[TestCase("\rSomething")] // Starts with carriage return
|
|
[TestCase("Something\r")] // Ends with carriage return
|
|
[TestCase("\nSomething")] // Starts with line feed
|
|
[TestCase("Something\n")] // Ends with line feed
|
|
[TestCase(" Something")] // Starts with space
|
|
[TestCase("Something ")] // Ends with space
|
|
[TestCase(" Something ")] // Starts and ends with space
|
|
public void EncodeCsvField_WhitespaceAtFrontOrBack_ShouldBeWrapped(string field)
|
|
{
|
|
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
|
var writer = GetWriterForEncodingTests(null, null, null);
|
|
|
|
// If: I CSV encode a field that has forbidden characters in it
|
|
string output = writer.EncodeCsvField(field);
|
|
|
|
// Then: It should wrap it in quotes
|
|
Assert.True(Regex.IsMatch(output, "^\".*\"$", RegexOptions.Singleline));
|
|
}
|
|
|
|
[TestCase("Something")]
|
|
[TestCase("Something valid.")]
|
|
[TestCase("Something\tvalid")]
|
|
public void EncodeCsvField_ShouldNotWrap(string field)
|
|
{
|
|
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
|
var writer = GetWriterForEncodingTests(null, null, null);
|
|
|
|
// If: I CSV encode a field that does not have forbidden characters in it
|
|
string output = writer.EncodeCsvField(field);
|
|
|
|
// Then: It should not wrap it in quotes
|
|
Assert.False(Regex.IsMatch(output, "^\".*\"$"));
|
|
}
|
|
|
|
[TestCase(null, "Some\"thing", "\"Some\"\"thing\"")] // Default identifier
|
|
[TestCase("|$", "Some|thing", "|Some||thing|")] // Custom identifier
|
|
public void EncodeCsvField_ContainsTextIdentifier_DoublesIdentifierAndWraps(
|
|
string configuredIdentifier,
|
|
string input,
|
|
string expectedOutput)
|
|
{
|
|
// Setup: Create CsvFileStreamWriter that specifies the text identifier and field separator
|
|
var writer = GetWriterForEncodingTests(null, configuredIdentifier, null);
|
|
|
|
// If: I CSV encode a field that has a double quote in it,
|
|
string output = writer.EncodeCsvField(input);
|
|
|
|
// Then: It should be replaced with double double quotes
|
|
Assert.AreEqual(expectedOutput, output);
|
|
}
|
|
|
|
[Test]
|
|
public void EncodeCsvField_Null()
|
|
{
|
|
// Setup: Create CsvFileStreamWriter
|
|
var writer = GetWriterForEncodingTests(null, null, null);
|
|
|
|
// If: I CSV encode a null
|
|
string output = writer.EncodeCsvField(null);
|
|
|
|
// Then: there should be a string version of null returned
|
|
Assert.AreEqual("NULL", output);
|
|
}
|
|
|
|
[Test]
|
|
public void WriteRow_WithoutColumnSelection()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has no selection made
|
|
// ... Create a set of data to write
|
|
// ... Create a memory location to store the data
|
|
var requestParams = new SaveResultsAsCsvRequestParams();
|
|
var (columns, data) = GetTestValues(2);
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I write a row
|
|
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
|
{
|
|
writer.WriteRow(data, columns);
|
|
}
|
|
|
|
// Then: It should write one line with 2 items, comma delimited
|
|
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.AreEqual(1, lines.Length);
|
|
|
|
string[] values = lines[0].Split(',');
|
|
Assert.AreEqual(2, values.Length);
|
|
}
|
|
|
|
[Test]
|
|
public void WriteRow_WithColumnSelection()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that selects n-1 columns from the front and back
|
|
// ... Create a set of data to write
|
|
// ... Create a memory location to store the data
|
|
var requestParams = new SaveResultsAsCsvRequestParams
|
|
{
|
|
ColumnStartIndex = 1,
|
|
ColumnEndIndex = 2,
|
|
RowStartIndex = 0, // Including b/c it is required to be a "save selection"
|
|
RowEndIndex = 10
|
|
};
|
|
var (columns, data) = GetTestValues(4);
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I write a row
|
|
SaveAsCsvFileStreamWriter writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns);
|
|
using (writer)
|
|
{
|
|
writer.WriteRow(data, columns);
|
|
}
|
|
|
|
// Then:
|
|
// ... It should have written one line
|
|
var lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.AreEqual(1, lines.Length);
|
|
|
|
// ... The line should have two, comma separated values
|
|
string[] dataValues = lines[0].Split(',');
|
|
Assert.AreEqual(2, dataValues.Length);
|
|
for (int i = 1; i <= 2; i++)
|
|
{
|
|
Assert.AreEqual(data[i].DisplayValue, dataValues[i - 1]);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void WriteRow_CustomDelimiter()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has custom delimiter say pipe("|") then this delimiter should be used
|
|
// ... Create a set of data to write
|
|
// ... Create a memory location to store the data
|
|
var requestParams = new SaveResultsAsCsvRequestParams
|
|
{
|
|
Delimiter = "|",
|
|
IncludeHeaders = true
|
|
};
|
|
var (columns, data) = GetTestValues(2);
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I write a row
|
|
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
|
{
|
|
writer.WriteRow(data, columns);
|
|
}
|
|
|
|
// Then:
|
|
// ... It should have written two lines
|
|
string[] lines = ParseWriterOutput(output, Environment.NewLine);
|
|
Assert.AreEqual(2, lines.Length);
|
|
|
|
// ... It should have written a header line with two, pipe("|") separated names
|
|
string[] headerValues = lines[0].Split('|');
|
|
Assert.AreEqual(2, headerValues.Length);
|
|
for (int i = 0; i < columns.Length; i++)
|
|
{
|
|
Assert.AreEqual(columns[i].ColumnName, headerValues[i]);
|
|
}
|
|
|
|
// Note: No need to check values, it is done as part of the previous tests
|
|
}
|
|
|
|
[Test]
|
|
public void WriteRow_CustomLineSeparator()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that has custom line separator
|
|
// ... Create a set of data to write
|
|
// ... Create a memory location to store the data
|
|
var requestParams = new SaveResultsAsCsvRequestParams
|
|
{
|
|
LineSeperator = "$$",
|
|
IncludeHeaders = true
|
|
};
|
|
var (columns, data) = GetTestValues(2);
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I set write a row
|
|
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
|
{
|
|
writer.WriteRow(data, columns);
|
|
}
|
|
|
|
// Then:
|
|
// ... The lines should be split by the custom line separator
|
|
var lines = ParseWriterOutput(output, "$$");
|
|
Assert.AreEqual(2, lines.Length);
|
|
}
|
|
|
|
[Test]
|
|
public void WriteRow_CustomEncoding()
|
|
{
|
|
// Setup:
|
|
// ... Create a request params that uses a custom encoding
|
|
// ... Create a set of data to write
|
|
// ... Create a memory location to store the data
|
|
var requestParams = new SaveResultsAsCsvRequestParams
|
|
{
|
|
Encoding = "Windows-1252"
|
|
};
|
|
var data = new[] { new DbCellValue { DisplayValue = "ü" } };
|
|
var columns = new[] { new DbColumnWrapper(new TestDbColumn("column1")) };
|
|
byte[] output = new byte[8192];
|
|
|
|
// If: I write a row
|
|
using (var writer = new SaveAsCsvFileStreamWriter(new MemoryStream(output), requestParams, columns))
|
|
{
|
|
writer.WriteRow(data, columns);
|
|
}
|
|
|
|
// Then:
|
|
// ... It should have written the umlaut using the encoding Windows-1252
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
string outputString = Encoding.GetEncoding("Windows-1252").GetString(output).TrimEnd('\0', '\r', '\n');
|
|
Assert.AreEqual("ü", outputString);
|
|
|
|
}
|
|
|
|
private static (DbColumnWrapper[] columns, DbCellValue[] cells) GetTestValues(int columnCount)
|
|
{
|
|
var data = new DbCellValue[columnCount];
|
|
var columns = new DbColumnWrapper[columnCount];
|
|
for (int i = 0; i < columnCount; i++)
|
|
{
|
|
data[i] = new DbCellValue { DisplayValue = $"item{i}"};
|
|
columns[i] = new DbColumnWrapper(new TestDbColumn($"column{i}"));
|
|
}
|
|
return (columns, data);
|
|
}
|
|
|
|
private static SaveAsCsvFileStreamWriter GetWriterForEncodingTests(string delimiter, string identifier, string lineSeparator)
|
|
{
|
|
var settings = new SaveResultsAsCsvRequestParams
|
|
{
|
|
Delimiter = delimiter,
|
|
IncludeHeaders = false,
|
|
LineSeperator = lineSeparator,
|
|
TextIdentifier = identifier,
|
|
};
|
|
var mockStream = Stream.Null;
|
|
var mockColumns = Array.Empty<DbColumnWrapper>();
|
|
return new SaveAsCsvFileStreamWriter(mockStream, settings, mockColumns);
|
|
}
|
|
|
|
private static string[] ParseWriterOutput(byte[] output, string lineSeparator)
|
|
{
|
|
string outputString = Encoding.UTF8.GetString(output).Trim('\0');
|
|
string[] lines = outputString.Split(new[] { lineSeparator }, StringSplitOptions.None);
|
|
|
|
// Make sure the file ends with a new line and return all but the meaningful lines
|
|
Assert.IsEmpty(lines[lines.Length - 1]);
|
|
return lines.Take(lines.Length - 1).ToArray();
|
|
}
|
|
}
|
|
}
|