-
Notifications
You must be signed in to change notification settings - Fork 199
Expand file tree
/
Copy pathProcedureAndFunctionToolsUnitTests.cs
More file actions
296 lines (258 loc) · 11.4 KB
/
ProcedureAndFunctionToolsUnitTests.cs
File metadata and controls
296 lines (258 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Extensions.Logging;
using Moq;
using Mssql.McpServer;
namespace MssqlMcp.Tests
{
/// <summary>
/// Unit tests for stored procedure and function tools.
/// These test the business logic and parameter validation without database dependencies.
/// </summary>
public sealed class ProcedureAndFunctionToolsUnitTests
{
private readonly Mock<ISqlConnectionFactory> _connectionFactoryMock;
private readonly Mock<ILogger<Tools>> _loggerMock;
private readonly Tools _tools;
public ProcedureAndFunctionToolsUnitTests()
{
_connectionFactoryMock = new Mock<ISqlConnectionFactory>();
_loggerMock = new Mock<ILogger<Tools>>();
_tools = new Tools(_connectionFactoryMock.Object, _loggerMock.Object);
}
#region CreateProcedure Tests
[Theory]
[InlineData("CREATE PROCEDURE dbo.TestProc AS BEGIN SELECT 1 END")]
[InlineData("CREATE OR ALTER PROCEDURE TestProc AS SELECT * FROM Users")]
[InlineData("create procedure MyProc (@id int) as begin select @id end")]
[InlineData("CREATE PROCEDURE [dbo].[My Proc] AS BEGIN PRINT 'Hello' END")]
public void CreateProcedure_ValidatesValidCreateStatements(string sql)
{
// Test that valid CREATE PROCEDURE statements pass validation
var trimmedSql = sql.Trim();
Assert.True(trimmedSql.StartsWith("CREATE", StringComparison.OrdinalIgnoreCase));
Assert.Contains("PROCEDURE", trimmedSql, StringComparison.OrdinalIgnoreCase);
}
[Theory]
[InlineData("SELECT * FROM Users")]
[InlineData("UPDATE Users SET Name = 'Test'")]
[InlineData("CREATE TABLE Test (Id INT)")]
[InlineData("CREATE FUNCTION TestFunc() RETURNS INT AS BEGIN RETURN 1 END")]
[InlineData("DROP PROCEDURE TestProc")]
[InlineData("ALTER PROCEDURE TestProc AS BEGIN SELECT 2 END")]
public void CreateProcedure_RejectsNonCreateProcedureStatements(string sql)
{
// Test that non-CREATE PROCEDURE statements are rejected
var trimmedSql = sql.Trim();
var isValidCreateProcedure = trimmedSql.StartsWith("CREATE", StringComparison.OrdinalIgnoreCase) &&
trimmedSql.Contains("PROCEDURE", StringComparison.OrdinalIgnoreCase);
Assert.False(isValidCreateProcedure);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void CreateProcedure_RejectsEmptyOrWhitespaceSql(string sql)
{
// Test that empty or whitespace SQL is rejected
Assert.True(string.IsNullOrWhiteSpace(sql));
}
#endregion
#region CreateFunction Tests
[Theory]
[InlineData("CREATE FUNCTION dbo.TestFunc() RETURNS INT AS BEGIN RETURN 1 END")]
[InlineData("CREATE OR ALTER FUNCTION TestFunc(@id int) RETURNS TABLE AS RETURN SELECT @id as Id")]
[InlineData("create function MyFunc (@param varchar(50)) returns varchar(100) as begin return @param + ' processed' end")]
[InlineData("CREATE FUNCTION [dbo].[My Function] () RETURNS INT AS BEGIN RETURN 42 END")]
public void CreateFunction_ValidatesValidCreateStatements(string sql)
{
// Test that valid CREATE FUNCTION statements pass validation
var trimmedSql = sql.Trim();
Assert.True(trimmedSql.StartsWith("CREATE", StringComparison.OrdinalIgnoreCase));
Assert.Contains("FUNCTION", trimmedSql, StringComparison.OrdinalIgnoreCase);
}
[Theory]
[InlineData("SELECT * FROM Users")]
[InlineData("UPDATE Users SET Name = 'Test'")]
[InlineData("CREATE TABLE Test (Id INT)")]
[InlineData("CREATE PROCEDURE TestProc AS BEGIN SELECT 1 END")]
[InlineData("DROP FUNCTION TestFunc")]
[InlineData("ALTER FUNCTION TestFunc() RETURNS INT AS BEGIN RETURN 2 END")]
public void CreateFunction_RejectsNonCreateFunctionStatements(string sql)
{
// Test that non-CREATE FUNCTION statements are rejected
var trimmedSql = sql.Trim();
var isValidCreateFunction = trimmedSql.StartsWith("CREATE", StringComparison.OrdinalIgnoreCase) &&
trimmedSql.Contains("FUNCTION", trimmedSql, StringComparison.OrdinalIgnoreCase);
Assert.False(isValidCreateFunction);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void CreateFunction_RejectsEmptyOrWhitespaceSql(string sql)
{
// Test that empty or whitespace SQL is rejected
Assert.True(string.IsNullOrWhiteSpace(sql));
}
#endregion
#region ExecuteStoredProcedure Tests
[Fact]
public void ExecuteStoredProcedure_ValidatesParameterTypes()
{
// Test that parameter dictionaries can handle various data types
var parameters = new Dictionary<string, object>
{
{ "StringParam", "test" },
{ "IntParam", 42 },
{ "DoubleParam", 3.14 },
{ "BoolParam", true },
{ "DateParam", DateTime.Now },
{ "NullParam", null! }
};
Assert.Equal(6, parameters.Count);
Assert.IsType<string>(parameters["StringParam"]);
Assert.IsType<int>(parameters["IntParam"]);
Assert.IsType<double>(parameters["DoubleParam"]);
Assert.IsType<bool>(parameters["BoolParam"]);
Assert.IsType<DateTime>(parameters["DateParam"]);
Assert.Null(parameters["NullParam"]);
}
[Theory]
[InlineData("ValidParam")]
[InlineData("Another_Valid123")]
[InlineData("@ParamWithAt")]
[InlineData("CamelCaseParam")]
[InlineData("snake_case_param")]
public void ExecuteStoredProcedure_AcceptsValidParameterNames(string paramName)
{
// Test that valid parameter names are accepted
var parameters = new Dictionary<string, object> { { paramName, "value" } };
Assert.True(parameters.ContainsKey(paramName));
Assert.Equal("value", parameters[paramName]);
}
[Fact]
public void ExecuteStoredProcedure_HandlesEmptyParameters()
{
// Test that null or empty parameter dictionary is handled
Dictionary<string, object>? nullParams = null;
var emptyParams = new Dictionary<string, object>();
Assert.Null(nullParams);
Assert.NotNull(emptyParams);
Assert.Empty(emptyParams);
}
#endregion
#region ExecuteFunction Tests
[Fact]
public void ExecuteFunction_ValidatesParameterTypes()
{
// Test that parameter dictionaries can handle various data types for functions
var parameters = new Dictionary<string, object>
{
{ "Id", 1 },
{ "Name", "TestName" },
{ "StartDate", DateTime.Today },
{ "IsActive", true },
{ "Score", 95.5 }
};
Assert.Equal(5, parameters.Count);
Assert.Contains("Id", parameters.Keys);
Assert.Contains("Name", parameters.Keys);
Assert.Contains("StartDate", parameters.Keys);
Assert.Contains("IsActive", parameters.Keys);
Assert.Contains("Score", parameters.Keys);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void ExecuteFunction_ValidatesEmptyFunctionName(string functionName)
{
// Test function name validation
Assert.True(string.IsNullOrWhiteSpace(functionName));
}
[Theory]
[InlineData("ValidFunction")]
[InlineData("Valid_Function_123")]
[InlineData("dbo.ValidFunction")]
[InlineData("[schema].[My Function]")]
public void ExecuteFunction_AcceptsValidFunctionNames(string functionName)
{
// Test function name validation for valid names
Assert.False(string.IsNullOrWhiteSpace(functionName));
Assert.True(functionName.Length > 0);
}
#endregion
#region General Validation Tests
[Fact]
public void Tools_Constructor_AcceptsValidDependencies()
{
// Test that Tools can be constructed with mocked dependencies
var factory = new Mock<ISqlConnectionFactory>();
var logger = new Mock<ILogger<Tools>>();
var tools = new Tools(factory.Object, logger.Object);
Assert.NotNull(tools);
}
[Fact]
public void SqlConnectionFactory_Interface_CanBeMocked()
{
// Test that the interface exists and can be mocked
Assert.NotNull(_connectionFactoryMock);
Assert.NotNull(_connectionFactoryMock.Object);
}
[Theory]
[InlineData("dbo.MyProcedure")]
[InlineData("schema.MyFunction")]
[InlineData("[My Schema].[My Object]")]
[InlineData("SimpleObject")]
public void DatabaseObjectNames_ValidateSchemaQualifiedNames(string objectName)
{
// Test that schema-qualified names are handled properly
Assert.False(string.IsNullOrWhiteSpace(objectName));
// Check if it's schema-qualified
var hasSchema = objectName.Contains('.');
if (hasSchema)
{
var parts = objectName.Split('.');
Assert.True(parts.Length >= 2);
Assert.All(parts, part => Assert.False(string.IsNullOrWhiteSpace(part.Trim('[', ']'))));
}
}
[Fact]
public void ParameterDictionary_HandlesNullValues()
{
// Test that parameter dictionaries can handle null values
var parameters = new Dictionary<string, object>
{
{ "NullParam", null! },
{ "StringParam", "value" },
{ "IntParam", 42 }
};
Assert.Equal(3, parameters.Count);
Assert.Null(parameters["NullParam"]);
Assert.Equal("value", parameters["StringParam"]);
Assert.Equal(42, parameters["IntParam"]);
}
[Fact]
public void ParameterDictionary_HandlesVariousTypes()
{
// Test that parameter dictionaries can handle various data types
var parameters = new Dictionary<string, object>
{
{ "StringParam", "test" },
{ "IntParam", 42 },
{ "DoubleParam", 3.14 },
{ "BoolParam", true },
{ "DateParam", DateTime.Now },
{ "DecimalParam", 123.45m },
{ "GuidParam", Guid.NewGuid() }
};
Assert.Equal(7, parameters.Count);
Assert.IsType<string>(parameters["StringParam"]);
Assert.IsType<int>(parameters["IntParam"]);
Assert.IsType<double>(parameters["DoubleParam"]);
Assert.IsType<bool>(parameters["BoolParam"]);
Assert.IsType<DateTime>(parameters["DateParam"]);
Assert.IsType<decimal>(parameters["DecimalParam"]);
Assert.IsType<Guid>(parameters["GuidParam"]);
}
#endregion
}
}