Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace PostgresIntegrationTests.CRUD;

public class PostgresBatchConformanceTests(PostgresFixture fixture)
: BatchConformanceTests<string>(fixture), IClassFixture<PostgresFixture>
public class PostgresBatchConformanceTests(PostgresSimpleModelFixture fixture)
: BatchConformanceTests<string>(fixture), IClassFixture<PostgresSimpleModelFixture>
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace PostgresIntegrationTests.CRUD;

public class PostgresGenericDataModelConformanceTests(PostgresFixture fixture)
: GenericDataModelConformanceTests<string>(fixture), IClassFixture<PostgresFixture>
public class PostgresGenericDataModelConformanceTests(PostgresGenericDataModelFixture fixture)
: GenericDataModelConformanceTests<string>(fixture), IClassFixture<PostgresGenericDataModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using PostgresIntegrationTests.Support;
using VectorDataSpecificationTests.CRUD;
using Xunit;

namespace PostgresIntegrationTests.CRUD;

public class PostgresRecordConformanceTests(PostgresSimpleModelFixture fixture)
: RecordConformanceTests<string>(fixture), IClassFixture<PostgresSimpleModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using PostgresIntegrationTests.Support;
using VectorDataSpecificationTests.Collections;
using Xunit;

namespace PostgresIntegrationTests.Collections;

public class PostgresCollectionConformanceTests(PostgresFixture fixture)
: CollectionConformanceTests<string>(fixture), IClassFixture<PostgresFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace PostgresIntegrationTests.Support;

public class PostgresGenericDataModelFixture : GenericDataModelFixture<string>
{
public override TestStore TestStore => PostgresTestStore.Instance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace PostgresIntegrationTests.Support;

public class PostgresSimpleModelFixture : SimpleModelFixture<string>
{
public override TestStore TestStore => PostgresTestStore.Instance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using RedisIntegrationTests.Support;
using VectorDataSpecificationTests.CRUD;
using Xunit;

namespace RedisIntegrationTests.CRUD;

public class RedisGenericDataModelConformanceTests(RedisGenericDataModelFixture fixture)
: GenericDataModelConformanceTests<string>(fixture), IClassFixture<RedisGenericDataModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using RedisIntegrationTests.Support;
using VectorDataSpecificationTests.CRUD;
using Xunit;

namespace RedisIntegrationTests.CRUD;

public class RedisRecordConformanceTests(RedisSimpleModelFixture fixture)
: RecordConformanceTests<string>(fixture), IClassFixture<RedisSimpleModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace RedisIntegrationTests.Support;

public class RedisFixture : VectorStoreFixture
{
public override TestStore TestStore => RedisTestStore.Instance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace RedisIntegrationTests.Support;

public class RedisGenericDataModelFixture : GenericDataModelFixture<string>
{
public override TestStore TestStore => RedisTestStore.Instance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace RedisIntegrationTests.Support;

public class RedisSimpleModelFixture : SimpleModelFixture<string>
{
public override TestStore TestStore => RedisTestStore.Instance;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace SqlServerIntegrationTests.CRUD;

public class SqlServerBatchConformanceTests(SqlServerFixture fixture)
: BatchConformanceTests<string>(fixture), IClassFixture<SqlServerFixture>
public class SqlServerBatchConformanceTests(SqlServerSimpleModelFixture fixture)
: BatchConformanceTests<string>(fixture), IClassFixture<SqlServerSimpleModelFixture>
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace SqlServerIntegrationTests.CRUD;

public class SqlServerGenericDataModelConformanceTests(SqlServerFixture fixture)
: GenericDataModelConformanceTests<string>(fixture), IClassFixture<SqlServerFixture>
public class SqlServerGenericDataModelConformanceTests(SqlServerGenericDataModelFixture fixture)
: GenericDataModelConformanceTests<string>(fixture), IClassFixture<SqlServerGenericDataModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using SqlServerIntegrationTests.Support;
using VectorDataSpecificationTests.CRUD;
using Xunit;

namespace SqlServerIntegrationTests.CRUD;

public class SqlServerRecordConformanceTests(SqlServerSimpleModelFixture fixture)
: RecordConformanceTests<string>(fixture), IClassFixture<SqlServerSimpleModelFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using SqlServerIntegrationTests.Support;
using VectorDataSpecificationTests.Collections;
using Xunit;

namespace SqlServerIntegrationTests.Collections;

public class SqlServerCollectionConformanceTests(SqlServerFixture fixture)
: CollectionConformanceTests<string>(fixture), IClassFixture<SqlServerFixture>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace SqlServerIntegrationTests.Support;

public class SqlServerGenericDataModelFixture : GenericDataModelFixture<string>
{
public override TestStore TestStore => SqlServerTestStore.Instance;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.

using VectorDataSpecificationTests.Support;

namespace SqlServerIntegrationTests.Support;

public class SqlServerSimpleModelFixture : SimpleModelFixture<string>
{
public override TestStore TestStore => SqlServerTestStore.Instance;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,150 @@

namespace VectorDataSpecificationTests.CRUD;

public abstract class BatchConformanceTests<TKey>(VectorStoreFixture fixture)
: ConformanceTestsBase<TKey, SimpleModel<TKey>>(fixture) where TKey : notnull
public abstract class BatchConformanceTests<TKey>(SimpleModelFixture<TKey> fixture) where TKey : notnull
{
[ConditionalFact]
public async Task UpsertBatchAsync_EmptyBatch_DoesNotThrow()
public async Task GetBatchAsyncThrowsArgumentNullExceptionForNullKeys()
{
await this.ExecuteAsync(async collection =>
{
Assert.Empty(await collection.UpsertBatchAsync([]).ToArrayAsync());
});
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => fixture.Collection.GetBatchAsync(keys: null!).ToArrayAsync().AsTask());
Assert.Equal("keys", ex.ParamName);
}

[ConditionalFact]
public async Task DeleteBatchAsync_EmptyBatch_DoesNotThrow()
public async Task GetBatchAsyncDoesNotThrowForEmptyBatch()
{
await this.ExecuteAsync(async collection =>
{
await collection.DeleteBatchAsync([]);
});
Assert.Empty(await fixture.Collection.GetBatchAsync([]).ToArrayAsync());
}

[ConditionalFact]
public async Task GetBatchAsync_EmptyBatch_DoesNotThrow()
public Task GetBatchAsyncReturnsInsertedRecords_WithVectors()
=> this.GetBatchAsyncReturnsInsertedRecords(includeVectors: true);

[ConditionalFact]
public Task GetBatchAsyncReturnsInsertedRecords_WithoutVectors()
=> this.GetBatchAsyncReturnsInsertedRecords(includeVectors: false);

private async Task GetBatchAsyncReturnsInsertedRecords(bool includeVectors)
{
await this.ExecuteAsync(async collection =>
var expectedRecords = fixture.TestData.Take(2); // the last two records can get deleted by other tests
var ids = expectedRecords.Select(record => record.Id);

var received = await fixture.Collection.GetBatchAsync(ids, new() { IncludeVectors = includeVectors }).ToArrayAsync();

foreach (var record in expectedRecords)
{
Assert.Empty(await collection.GetBatchAsync([]).ToArrayAsync());
});
record.AssertEqual(this.GetRecord(received, record.Id), includeVectors);
}
}

[ConditionalFact]
public async Task UpsertBatchAsync_NullBatch_ThrowsArgumentNullException()
public async Task UpsertBatchAsyncThrowsArgumentNullExceptionForNullBatch()
{
await this.ExecuteAsync(async collection =>
{
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => collection.UpsertBatchAsync(records: null!).ToArrayAsync().AsTask());
Assert.Equal("records", ex.ParamName);
});
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => fixture.Collection.UpsertBatchAsync(records: null!).ToArrayAsync().AsTask());
Assert.Equal("records", ex.ParamName);
}

[ConditionalFact]
public async Task UpsertBatchAsyncDoesNotThrowForEmptyBatch()
{
Assert.Empty(await fixture.Collection.UpsertBatchAsync([]).ToArrayAsync());
}

[ConditionalFact]
public async Task DeleteBatchAsync_NullKeys_ThrowsArgumentNullException()
public Task UpsertBatchAsyncCanInsertNewRecord_WithVectors()
=> this.UpsertBatchAsyncCanInsertNewRecords(includeVectors: true);

[ConditionalFact]
public Task UpsertBatchAsyncCanInsertNewRecord_WithoutVectors()
=> this.UpsertBatchAsyncCanInsertNewRecords(includeVectors: false);

private async Task UpsertBatchAsyncCanInsertNewRecords(bool includeVectors)
{
await this.ExecuteAsync(async collection =>
var collection = fixture.Collection;
SimpleModel<TKey>[] inserted = Enumerable.Range(0, 10).Select(i => new SimpleModel<TKey>()
{
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => collection.DeleteBatchAsync(keys: null!));
Assert.Equal("keys", ex.ParamName);
});
Id = fixture.GenerateNextKey<TKey>(),
Number = 100 + i,
Text = i.ToString(),
Floats = Enumerable.Range(0, SimpleModel<TKey>.DimensionCount).Select(j => (float)(i + j)).ToArray()
}).ToArray();
var keys = inserted.Select(record => record.Id).ToArray();

Assert.Empty(await collection.GetBatchAsync(keys).ToArrayAsync());
var receivedKeys = await collection.UpsertBatchAsync(inserted).ToArrayAsync();
Assert.Equal(keys.ToHashSet(), receivedKeys.ToHashSet()); // .ToHashSet() to ignore order

var received = await collection.GetBatchAsync(keys, new() { IncludeVectors = includeVectors }).ToArrayAsync();
foreach (var record in inserted)
{
record.AssertEqual(this.GetRecord(received, record.Id), includeVectors);
}
}

[ConditionalFact]
public async Task GetBatchAsync_NullKeys_ThrowsArgumentNullException()
public Task UpsertBatchAsyncCanUpdateExistingRecords_WithVectors()
=> this.UpsertBatchAsyncCanUpdateExistingRecords(includeVectors: true);

[ConditionalFact]
public Task UpsertBatchAsyncCanUpdateExistingRecords_WithoutVectors()
=> this.UpsertBatchAsyncCanUpdateExistingRecords(includeVectors: false);

private async Task UpsertBatchAsyncCanUpdateExistingRecords(bool includeVectors)
{
await this.ExecuteAsync(async collection =>
SimpleModel<TKey>[] inserted = Enumerable.Range(0, 10).Select(i => new SimpleModel<TKey>()
{
Id = fixture.GenerateNextKey<TKey>(),
Number = 100 + i,
Text = i.ToString(),
Floats = Enumerable.Range(0, SimpleModel<TKey>.DimensionCount).Select(j => (float)(i + j)).ToArray()
}).ToArray();
await fixture.Collection.UpsertBatchAsync(inserted).ToArrayAsync();

SimpleModel<TKey>[] updated = inserted.Select(i => new SimpleModel<TKey>()
{
Id = i.Id,
Text = i.Text + "updated",
Number = i.Number + 200,
Floats = i.Floats
}).ToArray();

var keys = await fixture.Collection.UpsertBatchAsync(updated).ToArrayAsync();
Assert.Equal(
updated.Select(r => r.Id).OrderBy(id => id).ToArray(),
keys.OrderBy(id => id).ToArray());

var received = await fixture.Collection.GetBatchAsync(keys, new() { IncludeVectors = includeVectors }).ToArrayAsync();
foreach (var record in updated)
{
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => collection.GetBatchAsync(keys: null!).ToArrayAsync().AsTask());
Assert.Equal("keys", ex.ParamName);
});
record.AssertEqual(this.GetRecord(received, record.Id), includeVectors);
}
}

[ConditionalFact]
public async Task DeleteBatchAsyncDoesNotThrowForEmptyBatch()
{
await fixture.Collection.DeleteBatchAsync([]);
}

[ConditionalFact]
public async Task DeleteBatchAsyncThrowsArgumentNullExceptionForNullKeys()
{
ArgumentNullException ex = await Assert.ThrowsAsync<ArgumentNullException>(() => fixture.Collection.DeleteBatchAsync(keys: null!));
Assert.Equal("keys", ex.ParamName);
}

[ConditionalFact]
public async Task DeleteBatchAsyncDeletesTheRecords()
{
TKey[] idsToRemove = [fixture.TestData[2].Id, fixture.TestData[3].Id];

Assert.NotEmpty(await fixture.Collection.GetBatchAsync(idsToRemove).ToArrayAsync());
await fixture.Collection.DeleteBatchAsync(idsToRemove);
Assert.Empty(await fixture.Collection.GetBatchAsync(idsToRemove).ToArrayAsync());
}

// The order of records in the received array is not guaranteed
// to match the order of keys in the requested keys array.
private SimpleModel<TKey> GetRecord(SimpleModel<TKey>[] received, TKey key)
=> received.Single(r => r.Id!.Equals(key));
}
Loading