exercism / exercism/fsharp-test-runner

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

Open
#272 6 comments 0 reactions 0 assignees View on GitHub
Dominant language
F#
Stars
5
Forks
11
Avg merge
10h 16m
Merged PRs (30d)
3

Description

## 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:

```text
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](https://github.com/exercism/fsharp-test-runner/blob/main/src/Exercism.TestRunner.FSharp/Rewrite.fs#L83). 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:

```text
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:

```fsharp
module RestApiTests

open FsUnit.Xunit
open Xunit
open RestApi

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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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:

```fsharp
module RestApiTests

open FsUnit.Xunit
open Xunit
open RestApi

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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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

[]
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
```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.