testcontainers / testcontainers-dotnet

A library to support tests with throwaway instances of Docker containers for all compatible .NET Standard versions.
https://dotnet.testcontainers.org
MIT License
3.82k stars 278 forks source link

[Bug]: System.DateTimeOffset Deserialization Error in Testcontainers #1253

Closed afiqsyed closed 2 months ago

afiqsyed commented 2 months ago

Testcontainers version

3.10.0

Using the latest Testcontainers version?

Yes

Host OS

Windows

Host arch

x86

.NET version

8.0

Docker version

Client:
 Cloud integration: v1.0.35+desktop.13
 Version:           26.0.0
 API version:       1.45
 Go version:        go1.21.8
 Git commit:        2ae903e
 Built:             Wed Mar 20 15:18:56 2024
 OS/Arch:           windows/amd64
 Context:           default

Server: Docker Desktop 4.29.0 (145265)
 Engine:
  Version:          26.0.0
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.8
  Git commit:       8b79278
  Built:            Wed Mar 20 15:18:01 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.28
  GitCommit:        ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker info

Client:
 Version:    26.0.0
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.13.1-desktop.1
    Path:     C:\Program Files\Docker\cli-plugins\docker-buildx.exe
  compose: Docker Compose (Docker Inc.)
    Version:  v2.26.1-desktop.1
    Path:     C:\Program Files\Docker\cli-plugins\docker-compose.exe
  debug: Get a shell into any image or container. (Docker Inc.)
    Version:  0.0.27
    Path:     C:\Program Files\Docker\cli-plugins\docker-debug.exe
  dev: Docker Dev Environments (Docker Inc.)
    Version:  v0.1.2
    Path:     C:\Program Files\Docker\cli-plugins\docker-dev.exe
  extension: Manages Docker extensions (Docker Inc.)
    Version:  v0.2.23
    Path:     C:\Program Files\Docker\cli-plugins\docker-extension.exe
  feedback: Provide feedback, right in your terminal! (Docker Inc.)
    Version:  v1.0.4
    Path:     C:\Program Files\Docker\cli-plugins\docker-feedback.exe
  init: Creates Docker-related starter files for your project (Docker Inc.)
    Version:  v1.1.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-init.exe
  sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.)
    Version:  0.6.0
    Path:     C:\Program Files\Docker\cli-plugins\docker-sbom.exe
  scout: Docker Scout (Docker Inc.)
    Version:  v1.6.3
    Path:     C:\Program Files\Docker\cli-plugins\docker-scout.exe

Server:
 Containers: 2
  Running: 2
  Paused: 0
  Stopped: 0
 Images: 261
 Server Version: 26.0.0
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
 runc version: v1.1.12-0-g51d5e94
 init version: de40ad0
 Security Options:
  seccomp
   Profile: unconfined
 Kernel Version: 5.15.146.1-microsoft-standard-WSL2
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 8
 Total Memory: 15.47GiB
 Name: docker-desktop
 ID: a535b283-1890-4bd3-813c-1a414f0c0d31
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 HTTP Proxy: http.docker.internal:3128
 HTTPS Proxy: http.docker.internal:3128
 No Proxy: hubproxy.docker.internal
 Labels:
  com.docker.desktop.address=npipe://\\.\pipe\docker_cli
 Experimental: false
 Insecure Registries:
  hubproxy.docker.internal:5555
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No blkio throttle.read_bps_device support
WARNING: No blkio throttle.write_bps_device support
WARNING: No blkio throttle.read_iops_device support
WARNING: No blkio throttle.write_iops_device support
WARNING: daemon is not using the default seccomp profile

What happened?

When running two integration tests using the Testcontainers.MsSql library, the first test passes, but the second test consistently fails with a System.NotImplementedException. The error is related to deserializing System.DateTimeOffset back to System.DateTime. I tried to run the test one by one, both tests are passing. Problem happened when I tried to execute both tests together.

This is how I build my testcontainer in the constructor of the test class

_msSqlContainer = new MsSqlBuilder()
        .WithImage("mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04")
        .WithPassword("Mypassword")
        .Build();

I'm using Xunit, so I start my mssql testcontainer on InitializeAsync() and dispose it on DisposeAsync()

Here are how my tests look like. Both tests are actually the same, I just try to reproduce this issue.

[Fact]
public async Task Test1()
{
    // Arrange
    var body = """
        {
          "title": "IntegrationTest",
          "dueOn": "2024-09-16T12:33:04.276Z"
        }
        """;

    // Act
    var response = await _client.PostAsync("/api", new StringContent(body, default, "application/json"));

    // Assert
    response.EnsureSuccessStatusCode();
    Assert.Equal(System.Net.HttpStatusCode.Created, response.StatusCode);
}

[Fact]
public async Task Test2()
{
    // Arrange
    var body = """
        {
          "title": "IntegrationTest",
          "dueOn": "2024-09-16T12:33:04.276Z"
        }
        """;

    // Act
    var response = await _client.PostAsync("/api", new StringContent(body, default, "application/json"));

    // Assert
    response.EnsureSuccessStatusCode();
    Assert.Equal(System.Net.HttpStatusCode.Created, response.StatusCode);
}

Relevant log output

Message: 
System.NotImplementedException : Deserializing System.DateTimeOffset back to System.DateTime is not handled.

  Stack Trace: 
JsonIso8601AndUnixEpochDateConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
JsonSerializer.Deserialize(JsonReader reader, Type objectType)
JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
JsonSerializer.DeserializeObject[T](String json)
ImageOperations.InspectImageAsync(String name, CancellationToken cancellationToken)
DockerImageOperations.ByIdAsync(String id, CancellationToken ct) line 41
TestcontainersClient.RunAsync(IContainerConfiguration configuration, CancellationToken ct) line 298
DockerContainer.UnsafeCreateAsync(CancellationToken ct) line 415
DockerContainer.StartAsync(CancellationToken ct) line 279
ApiTests.InitializeAsync() line 26

Additional information

No response

HofmeisterAn commented 2 months ago

This is an issue in Docker.DotNet. Does your code change Json.NET's DefaultSettings somewhere? Right now, I assume that the first test changes the static Json.NET's default settings, and as a result, the second test fails because instantiating a new DockerClient and the necessary JsonConvert results in a broken configuration (or something like that). Still wondering how CanConvert(Type) returns true. I looked into Docker.DotNet but cannot reproduce it.

HofmeisterAn commented 2 months ago

Which version of Newtonsoft.Json are you using?

afiqsyed commented 2 months ago

@HofmeisterAn I manage to resolve this issue by updating Microsoft.AspNetCore.Mvc.Testing from version 8.0.0 to 8.0.8

HofmeisterAn commented 2 months ago

Oh, that is unexpected. Does it include a known fix? Do you know of any related issues?