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
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Datasync.Server;
using CommunityToolkit.Datasync.TestCommon.Models;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace CommunityToolkit.Datasync.Client.Test.Helpers;

[ExcludeFromCodeCoverage]
public class ByteVersionMovie
{
public ByteVersionMovie()
{
}

public ByteVersionMovie(object source)
{
if (source is ITableData metadata)
{
Id = metadata.Id;
Deleted = metadata.Deleted;
UpdatedAt = metadata.UpdatedAt;
Version = [..metadata.Version];
}

if (source is IMovie movie)
{
BestPictureWinner = movie.BestPictureWinner;
Duration = movie.Duration;
Rating = movie.Rating;
ReleaseDate = movie.ReleaseDate;
Title = movie.Title;
Year = movie.Year;
}
}

[Key]
public string Id { get; set; }

[Column(TypeName = "INTEGER")]
public DateTimeOffset? UpdatedAt { get; set; }
public byte[] Version { get; set; }
public bool Deleted { get; set; }

public bool BestPictureWinner { get; set; }
public int Duration { get; set; }
public MovieRating Rating { get; set; } = MovieRating.Unrated;
public DateOnly ReleaseDate { get; set; }
public string Title { get; set; } = string.Empty;
public int Year { get; set; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class IntegrationDbContext(DbContextOptions<IntegrationDbContext> options
{
public DbSet<ClientMovie> Movies => Set<ClientMovie>();

public DbSet<ByteVersionMovie> ByteMovies => Set<ByteVersionMovie>();

public ServiceApplicationFactory Factory { get; set; }

public SqliteConnection Connection { get; set; }
Expand All @@ -28,6 +30,12 @@ protected override void OnDatasyncInitialization(DatasyncOfflineOptionsBuilder o
cfg.ClientName = "movies";
cfg.Endpoint = new Uri($"/{Factory.MovieEndpoint}", UriKind.Relative);
});

optionsBuilder.Entity<ByteVersionMovie>(cfg =>
{
cfg.ClientName = "movies";
cfg.Endpoint = new Uri($"/{Factory.MovieEndpoint}", UriKind.Relative);
});
}

protected override void Dispose(bool disposing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ public async Task PullAsync_ViaDbSet_Works()
}
}

[Fact]
public async Task PullAsync_ViaDbSet_Works_ByteVersion()
{
await this.context.ByteMovies.PullAsync();
List<ByteVersionMovie> movies = await this.context.ByteMovies.ToListAsync();

movies.Count.Should().Be(248);
foreach (ByteVersionMovie movie in movies)
{
InMemoryMovie serviceMovie = GetServerEntityById<InMemoryMovie>(movie.Id);
serviceMovie.Should().NotBeNull()
.And.BeEquivalentTo<IMovie>(serviceMovie)
.And.HaveEquivalentMetadataTo(serviceMovie);
}
}

[Fact]
public async Task PullAsync_ViaContext_Works()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,87 @@ public async Task PushAsync_Complex_Situation()
this.context.DatasyncOperationsQueue.Should().BeEmpty();

// Now use PullAsync() again - these should all be pulled down again
PullResult pullResults = await this.context.PullAsync();
PullResult pullResults = await this.context.Movies.PullAsync();
pullResults.IsSuccessful.Should().BeTrue();
pullResults.Additions.Should().Be(0);
pullResults.Deletions.Should().Be(0);
// The service always replaces additions and replacements - updating the last updatedAt.
pullResults.Replacements.Should().Be(moviesToReplace.Count + 1);
}

[Fact]
public async Task PushAsync_ByteVersion()
{
ResetInMemoryMovies();

PullResult initialPullResults = await this.context.ByteMovies.PullAsync();
initialPullResults.IsSuccessful.Should().BeTrue();
initialPullResults.Additions.Should().Be(248);
initialPullResults.Deletions.Should().Be(0);
initialPullResults.Replacements.Should().Be(0);
this.context.ByteMovies.Should().HaveCount(248);

// Let's add some new movies
ByteVersionMovie blackPanther = new(TestCommon.TestData.Movies.BlackPanther) { Id = Guid.NewGuid().ToString("N") };
this.context.ByteMovies.Add(blackPanther);
await this.context.SaveChangesAsync();

// And remove any movie that matches some criteria
List<ByteVersionMovie> moviesToDelete = await this.context.ByteMovies.Where(x => x.Duration > 180).ToListAsync();
this.context.ByteMovies.RemoveRange(moviesToDelete);
await this.context.SaveChangesAsync();

// Then replace all the Unrated movies with a rating of NC17
List<ByteVersionMovie> moviesToReplace = await this.context.ByteMovies.Where(x => x.Rating == MovieRating.Unrated).ToListAsync();
moviesToReplace.ForEach(r =>
{
r.Rating = MovieRating.NC17;
r.Title = r.Title.PadLeft('-');
this.context.ByteMovies.Update(r);
});
await this.context.SaveChangesAsync();

// Check the queue.
List<DatasyncOperation> operations = await this.context.DatasyncOperationsQueue.ToListAsync();
operations.Count.Should().Be(1 + moviesToDelete.Count + moviesToReplace.Count);
operations.Count(x => x.Kind is OperationKind.Add).Should().Be(1);
operations.Count(x => x.Kind is OperationKind.Delete).Should().Be(moviesToDelete.Count);
operations.Count(x => x.Kind is OperationKind.Replace).Should().Be(moviesToReplace.Count);

// Now push the results and check what we did
PushResult pushResults = await this.context.PushAsync();

// This little snippet of code is to aid debugging if this test fails
if (!pushResults.IsSuccessful)
{
foreach (KeyValuePair<string, ServiceResponse> failedRequest in pushResults.FailedRequests)
{
string id = failedRequest.Key;
ServiceResponse response = failedRequest.Value;
string jsonContent = string.Empty;
if (response.HasContent)
{
using StreamReader reader = new(response.ContentStream);
jsonContent = reader.ReadToEnd();
}

Console.WriteLine($"FAILED REQUEST FOR ID: {id}: {response.StatusCode}\n{jsonContent}");
}
}

pushResults.IsSuccessful.Should().BeTrue();
pushResults.CompletedOperations.Should().Be(1 + moviesToDelete.Count + moviesToReplace.Count);
this.context.DatasyncOperationsQueue.Should().BeEmpty();

// Now use PullAsync() again - these should all be pulled down again
PullResult pullResults = await this.context.ByteMovies.PullAsync();
pullResults.IsSuccessful.Should().BeTrue();
pullResults.Additions.Should().Be(0);
pullResults.Deletions.Should().Be(0);
// The service always replaces additions and replacements - updating the last updatedAt.
pullResults.Replacements.Should().Be(moviesToReplace.Count + 1);
}

[Fact]
public async Task PushAsync_Multithreaded()
{
Expand Down Expand Up @@ -176,7 +249,7 @@ public async Task PushAsync_Multithreaded()
this.context.DatasyncOperationsQueue.Should().BeEmpty();

// Now use PullAsync() again - these should all be pulled down again
PullResult pullResults = await this.context.PullAsync();
PullResult pullResults = await this.context.Movies.PullAsync();
pullResults.IsSuccessful.Should().BeTrue();
pullResults.Additions.Should().Be(0);
pullResults.Deletions.Should().Be(0);
Expand Down