Skip to content

Test runner Rewrite operation does not handle triple-quoted strings correctly #272

Open
@RamanBut-Husaim

Description

@RamanBut-Husaim

Source

https://forum.exercism.org/t/restapi-and-system-text-json-issue/15946

Description

The issue has been initially identified as part of the work on rest-api exercise for F# language. All tests are green locally, however exercism platform test runner considers all tests as failed with the following error:

System.Text.Json.JsonException : '\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
---- System.Text.Json.JsonReaderException : '\' is an invalid start of a property name. Expected a '"'. LineNumber: 0 | BytePositionInLine: 1.

The local debug showed that the issue is produced by the Rewrite.fs file. After the rewrite, the output of the test code has every " inside the string escaped with additional \, that makes the json inside the triple-quoted string invalid.

Here is the full error output of the test in JetBrains Rider:

System.Text.Json.JsonException: '\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.

System.Text.Json.JsonException
'\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
   at RestApi.RestApi..ctor(String database) in /Users/Rakkatakka/Exercism/fsharp/rest-api/RestApi.fs:line 37
   at RestApiTests.Add user() in /Users/Rakkatakka/Exercism/fsharp/rest-api/RestApiTests.fs:line 24
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

System.Text.Json.JsonReaderException
'\' is an invalid start of a property name. Expected a '"'. LineNumber: 0 | BytePositionInLine: 1.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)

The entire content of the malformed file is:

module RestApiTests

open FsUnit.Xunit
open Xunit
open RestApi

[<Fact>]
let ``No users`` () =
    let database = """{\\"users\\":[]}"""
    let url = "/users"
    let expected = """{\\"users\\":[]}"""
    let api = RestApi(database)
    api.Get url |> should equal expected

[<Fact>]
let ``Add user`` () =
    let database = """{\\"users\\":[]}"""
    let payload = """{\\"user\\":\\"Adam\\"}"""
    let url = "/add"

    let expected =
        """{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Get single user`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""

    let payload = """{\\"users\\":[\\"Bob\\"]}"""
    let url = "/users"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""

    let api = RestApi(database)
    api.Get(url, payload) |> should equal expected

[<Fact>]
let ``Both users have 0 balance`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""

    let payload =
        """{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Borrower has negative balance`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Chuck\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0}]}"""

    let payload =
        """{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":3.0,\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-6.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Lender has negative balance`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Chuck\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0}]}"""

    let payload =
        """{\\"lender\\":\\"Bob\\",\\"borrower\\":\\"Adam\\",\\"amount\\":3.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":0.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Lender owes borrower`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""

    let payload =
        """{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":2.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":1.0},\\"owed_by\\":{},\\"balance\\":-1.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":1.0},\\"balance\\":1.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Lender owes borrower less than new loan`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""

    let payload =
        """{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":4.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":1.0},\\"balance\\":1.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":1.0},\\"owed_by\\":{},\\"balance\\":-1.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact>]
let ``Lender owes borrower same as new loan`` () =
    let database =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""

    let payload =
        """{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""

    let url = "/iou"

    let expected =
        """{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

Quick Fix

The replacement of triple-quoted strings """ with a single quote ones " in the test file helps to address the issue. Here is the example of a test file that successfully passes the rewrite:

module RestApiTests

open FsUnit.Xunit
open Xunit
open RestApi

[<Fact>]
let ``No users`` () =
    let database = "{\"users\":[]}"
    let url = "/users"
    let expected = "{\"users\":[]}"
    let api = RestApi(database)
    api.Get url |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Add user`` () =
    let database = "{\"users\":[]}"
    let payload = "{\"user\":\"Adam\"}"
    let url = "/add"
    let expected = "{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}"
    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Get single user`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"

    let payload = "{\"users\":[\"Bob\"]}"
    let url = "/users"

    let expected =
        "{\"users\":[{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"

    let api = RestApi(database)
    api.Get(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Both users have 0 balance`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"

    let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":3.0},\"owed_by\":{},\"balance\":-3.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Borrower has negative balance`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Chuck\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0}]}"

    let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":3.0,\"Chuck\":3.0},\"owed_by\":{},\"balance\":-6.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender has negative balance`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Chuck\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0}]}"

    let payload = "{\"lender\":\"Bob\",\"borrower\":\"Adam\",\"amount\":3.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{\"Adam\":3.0},\"balance\":0.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"

    let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":2.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":1.0},\"owed_by\":{},\"balance\":-1.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":1.0},\"balance\":1.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower less than new loan`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"

    let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":4.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":1.0},\"balance\":1.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":1.0},\"owed_by\":{},\"balance\":-1.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower same as new loan`` () =
    let database =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"

    let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
    let url = "/iou"

    let expected =
        "{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"

    let api = RestApi(database)
    api.Post(url, payload) |> should equal expected

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions