Skip to content

Commit dab9013

Browse files
Add tests
1 parent c85f54c commit dab9013

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using NucliaDB.Generated;
3+
using Outcome;
4+
using Xunit;
5+
6+
#pragma warning disable CS8509 // C# compiler doesn't understand nested discriminated unions - use EXHAUSTION001 instead
7+
#pragma warning disable SA1512 // Single-line comments should not be followed by blank line
8+
9+
namespace NucliaDbClient.Tests;
10+
11+
/// <summary>
12+
/// Tests to verify that issue #142 is fixed: anyOf types are now properly resolved
13+
/// to their concrete types instead of being generated as 'object'.
14+
/// </summary>
15+
[Collection("NucliaDB Tests")]
16+
[TestCaseOrderer("NucliaDbClient.Tests.PriorityOrderer", "NucliaDbClient.Tests")]
17+
public class TypeSafetyTests
18+
{
19+
#region Setup
20+
private readonly IHttpClientFactory _httpClientFactory;
21+
private static string? _knowledgeBoxId;
22+
23+
public TypeSafetyTests(NucliaDbFixture fixture)
24+
{
25+
_ = fixture; // Ensure fixture is initialized
26+
var services = new ServiceCollection();
27+
services.AddHttpClient();
28+
var serviceProvider = services.BuildServiceProvider();
29+
_httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
30+
}
31+
32+
private HttpClient CreateHttpClient() => _httpClientFactory.CreateClient();
33+
#endregion
34+
35+
[Fact]
36+
[TestPriority(-1)]
37+
public async Task CreateKnowledgeBox_ForTypeTests()
38+
{
39+
var payload = new { slug = $"typesafe-kb-{Guid.NewGuid()}", title = "Type Safety Test KB" };
40+
41+
var result = await CreateHttpClient()
42+
.CreateKnowledgeBoxKbsAsync(body: payload, xNUCLIADBROLES: "MANAGER");
43+
44+
var kb = result switch
45+
{
46+
OkKnowledgeBoxObj(var value) => value,
47+
ErrorKnowledgeBoxObj(HttpError<string>.ExceptionError(var ex)) =>
48+
throw new InvalidOperationException("Failed to create knowledge box", ex),
49+
ErrorKnowledgeBoxObj(
50+
HttpError<string>.ErrorResponseError
51+
(var body, var statusCode, _)
52+
) => throw new InvalidOperationException(
53+
$"Failed to create knowledge box: HTTP {statusCode}: {body}"
54+
),
55+
};
56+
57+
Assert.NotNull(kb);
58+
Assert.NotNull(kb.Uuid);
59+
_knowledgeBoxId = kb.Uuid;
60+
}
61+
62+
/// <summary>
63+
/// Verifies that KnowledgeBoxObj.Config is typed as KnowledgeBoxConfig (not object).
64+
/// This is a compile-time check - if Config were 'object', the property access would fail.
65+
/// </summary>
66+
/// <returns>Task.</returns>
67+
[Fact]
68+
[TestPriority(1)]
69+
public async Task KnowledgeBoxObj_Config_IsTypedAsKnowledgeBoxConfig_NotObject()
70+
{
71+
// Arrange & Act
72+
var result = await CreateHttpClient()
73+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
74+
75+
var kb = result switch
76+
{
77+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
78+
ErrorKnowledgeBoxObjHTTPValidationError(
79+
HttpError<HTTPValidationError>.ExceptionError
80+
(var ex)
81+
) => throw new InvalidOperationException("API call failed with exception", ex),
82+
ErrorKnowledgeBoxObjHTTPValidationError(
83+
HttpError<HTTPValidationError>.ErrorResponseError
84+
(var body, var statusCode, _)
85+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
86+
};
87+
88+
// Assert - These assertions will only compile if Config is KnowledgeBoxConfig (not object)
89+
Assert.NotNull(kb);
90+
91+
// If Config is object, we can't access these properties without casting
92+
// The fact this compiles proves Config is KnowledgeBoxConfig
93+
if (kb.Config is { } config)
94+
{
95+
// Access Config properties in a type-safe manner
96+
var slug = config.Slug;
97+
var hiddenResourcesEnabled = config.HiddenResourcesEnabled;
98+
var hiddenResourcesHideOnCreation = config.HiddenResourcesHideOnCreation;
99+
100+
// These properties exist on KnowledgeBoxConfig but not on object
101+
Assert.NotNull(slug);
102+
103+
// Verify we can access the bool properties (wouldn't work if Config were object)
104+
// The fact that this compiles without casting proves the types are correct
105+
_ = hiddenResourcesEnabled;
106+
_ = hiddenResourcesHideOnCreation;
107+
}
108+
}
109+
110+
/// <summary>
111+
/// Verifies that KnowledgeBoxObj.Model is typed as SemanticModelMetadata (not object).
112+
/// </summary>
113+
/// <returns>Task.</returns>
114+
[Fact]
115+
[TestPriority(2)]
116+
public async Task KnowledgeBoxObj_Model_IsTypedAsSemanticModelMetadata_NotObject()
117+
{
118+
// Arrange & Act
119+
var result = await CreateHttpClient()
120+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
121+
122+
var kb = result switch
123+
{
124+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
125+
ErrorKnowledgeBoxObjHTTPValidationError(
126+
HttpError<HTTPValidationError>.ExceptionError
127+
(var ex)
128+
) => throw new InvalidOperationException("API call failed with exception", ex),
129+
ErrorKnowledgeBoxObjHTTPValidationError(
130+
HttpError<HTTPValidationError>.ErrorResponseError
131+
(var body, var statusCode, _)
132+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
133+
};
134+
135+
// Assert - These assertions will only compile if Model is SemanticModelMetadata (not object)
136+
Assert.NotNull(kb);
137+
138+
if (kb.Model is { } model)
139+
{
140+
// Access Model properties in a type-safe manner
141+
var similarityFunction = model.SimilarityFunction;
142+
var vectorDimension = model.VectorDimension;
143+
144+
// These properties exist on SemanticModelMetadata but not on object
145+
Assert.NotNull(similarityFunction);
146+
Assert.NotEmpty(similarityFunction);
147+
148+
// Vector dimension should be a positive integer if present
149+
if (vectorDimension.HasValue)
150+
{
151+
Assert.True(vectorDimension.Value > 0);
152+
}
153+
}
154+
}
155+
156+
/// <summary>
157+
/// Verifies we can access deeply nested properties in a type-safe manner.
158+
/// This proves the entire type hierarchy is properly resolved.
159+
/// </summary>
160+
/// <returns>Task.</returns>
161+
[Fact]
162+
[TestPriority(3)]
163+
public async Task KnowledgeBoxObj_AllowsDeepPropertyAccess_TypeSafely()
164+
{
165+
// Arrange & Act
166+
var result = await CreateHttpClient()
167+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
168+
169+
var kb = result switch
170+
{
171+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
172+
ErrorKnowledgeBoxObjHTTPValidationError(
173+
HttpError<HTTPValidationError>.ExceptionError
174+
(var ex)
175+
) => throw new InvalidOperationException("API call failed with exception", ex),
176+
ErrorKnowledgeBoxObjHTTPValidationError(
177+
HttpError<HTTPValidationError>.ErrorResponseError
178+
(var body, var statusCode, _)
179+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
180+
};
181+
182+
// Assert - Access multiple levels of properties without casting
183+
Assert.NotNull(kb);
184+
Assert.NotNull(kb.Uuid);
185+
Assert.NotNull(kb.Slug);
186+
187+
// Access Config properties - the fact that we can do this without casting proves type safety
188+
var configSlug = kb.Config?.Slug;
189+
var configTitle = kb.Config?.Title;
190+
191+
// The fact that we can access these properties without casting proves type safety
192+
// If Config/Model were 'object', we'd need explicit casts
193+
Assert.NotNull(configSlug);
194+
Assert.NotNull(configTitle);
195+
}
196+
197+
/// <summary>
198+
/// Verifies that we can use pattern matching with the properly typed properties.
199+
/// </summary>
200+
/// <returns>Task.</returns>
201+
[Fact]
202+
[TestPriority(4)]
203+
public async Task KnowledgeBoxObj_SupportsPatternMatching_OnTypedProperties()
204+
{
205+
// Arrange & Act
206+
var result = await CreateHttpClient()
207+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
208+
209+
var kb = result switch
210+
{
211+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
212+
ErrorKnowledgeBoxObjHTTPValidationError(
213+
HttpError<HTTPValidationError>.ExceptionError
214+
(var ex)
215+
) => throw new InvalidOperationException("API call failed with exception", ex),
216+
ErrorKnowledgeBoxObjHTTPValidationError(
217+
HttpError<HTTPValidationError>.ErrorResponseError
218+
(var body, var statusCode, _)
219+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
220+
};
221+
222+
// Assert - Use pattern matching on typed properties
223+
Assert.NotNull(kb);
224+
225+
// Pattern match on Config (only works if Config is KnowledgeBoxConfig, not object)
226+
var configDescription = kb.Config switch
227+
{
228+
{ Description: var desc } when !string.IsNullOrEmpty(desc) => desc,
229+
{ Description: _ } => "No description",
230+
null => "Config not available",
231+
};
232+
233+
Assert.NotNull(configDescription);
234+
235+
// Pattern match on Model (only works if Model is SemanticModelMetadata, not object)
236+
var modelDescription = kb.Model switch
237+
{
238+
{ SimilarityFunction: var sf, VectorDimension: var vd }
239+
when !string.IsNullOrEmpty(sf) => $"{sf} with dimension {vd}",
240+
{ SimilarityFunction: var sf } => sf,
241+
null => "Model not available",
242+
};
243+
244+
Assert.NotNull(modelDescription);
245+
}
246+
247+
/// <summary>
248+
/// Verifies that type information is preserved across multiple API calls.
249+
/// </summary>
250+
/// <returns>Task.</returns>
251+
[Fact]
252+
[TestPriority(5)]
253+
public async Task MultipleApiCalls_ReturnConsistentlyTypedObjects()
254+
{
255+
// First call
256+
var result1 = await CreateHttpClient()
257+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
258+
259+
var kb1 = result1 switch
260+
{
261+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
262+
ErrorKnowledgeBoxObjHTTPValidationError(
263+
HttpError<HTTPValidationError>.ExceptionError
264+
(var ex)
265+
) => throw new InvalidOperationException("API call failed with exception", ex),
266+
ErrorKnowledgeBoxObjHTTPValidationError(
267+
HttpError<HTTPValidationError>.ErrorResponseError
268+
(var body, var statusCode, _)
269+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
270+
};
271+
272+
// Second call
273+
var result2 = await CreateHttpClient()
274+
.KbKbKbidGetAsync(kbid: _knowledgeBoxId!, xNUCLIADBROLES: "READER");
275+
276+
var kb2 = result2 switch
277+
{
278+
OkKnowledgeBoxObjHTTPValidationError(var value) => value,
279+
ErrorKnowledgeBoxObjHTTPValidationError(
280+
HttpError<HTTPValidationError>.ExceptionError
281+
(var ex)
282+
) => throw new InvalidOperationException("API call failed with exception", ex),
283+
ErrorKnowledgeBoxObjHTTPValidationError(
284+
HttpError<HTTPValidationError>.ErrorResponseError
285+
(var body, var statusCode, _)
286+
) => throw new InvalidOperationException($"API call failed: HTTP {statusCode}: {body}"),
287+
};
288+
289+
// Assert - Both objects have properly typed Config and Model
290+
Assert.NotNull(kb1);
291+
Assert.NotNull(kb2);
292+
293+
// Access Config from both - proves type consistency
294+
var config1Slug = kb1.Config?.Slug;
295+
var config2Slug = kb2.Config?.Slug;
296+
297+
Assert.Equal(config1Slug, config2Slug);
298+
299+
// Access Model from both - proves type consistency
300+
var model1Func = kb1.Model?.SimilarityFunction;
301+
var model2Func = kb2.Model?.SimilarityFunction;
302+
303+
Assert.Equal(model1Func, model2Func);
304+
}
305+
306+
/// <summary>
307+
/// Compile-time type verification test.
308+
/// This method contains code that will ONLY compile if the types are correct.
309+
/// If Config or Model were 'object', this would not compile without casts.
310+
/// </summary>
311+
[Fact]
312+
[TestPriority(6)]
313+
public void CompileTimeTypeVerification_ProvesBugIsFix()
314+
{
315+
// This is a compile-time test - if it compiles, the types are correct
316+
317+
// Create a mock KnowledgeBoxObj
318+
var config = new KnowledgeBoxConfig(
319+
Slug: "test",
320+
Title: "Test",
321+
Description: "Test",
322+
LearningConfiguration: null,
323+
ExternalIndexProvider: null,
324+
ConfiguredExternalIndexProvider: null,
325+
Similarity: null,
326+
HiddenResourcesEnabled: false,
327+
HiddenResourcesHideOnCreation: false
328+
);
329+
330+
var model = new SemanticModelMetadata(
331+
SimilarityFunction: "cosine",
332+
VectorDimension: 768,
333+
DefaultMinScore: 0.7f
334+
);
335+
336+
var kb = new KnowledgeBoxObj(
337+
Slug: "test-kb",
338+
Uuid: Guid.NewGuid().ToString(),
339+
Config: config,
340+
Model: model
341+
);
342+
343+
// These assignments will ONLY compile if the types are correct
344+
var configFromKb = kb.Config; // Would fail if Config were object
345+
var modelFromKb = kb.Model; // Would fail if Model were object
346+
347+
// Access properties without casting
348+
var slug = configFromKb?.Slug;
349+
var similarityFunction = modelFromKb?.SimilarityFunction;
350+
var vectorDimension = modelFromKb?.VectorDimension;
351+
352+
// Assert
353+
Assert.NotNull(configFromKb);
354+
Assert.NotNull(modelFromKb);
355+
Assert.Equal("test", slug);
356+
Assert.Equal("cosine", similarityFunction);
357+
Assert.Equal(768, vectorDimension);
358+
}
359+
}

0 commit comments

Comments
 (0)