Skip to content

Commit 443e9e4

Browse files
Copilotxperiandri
andauthored
Add comprehensive test coverage for ToQueryString string escaping
Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/5ee4bb8d-2ee3-4b01-ad1e-b1052d41e738 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
1 parent 9a82472 commit 443e9e4

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

tests/FSharp.Data.GraphQL.Tests/AstExtensionsTests.fs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module FSharp.Data.GraphQL.Tests.AstExtensionsTests
55

66
open Xunit
7+
open FSharp.Data.GraphQL
78
open FSharp.Data.GraphQL.Parser
89
open FSharp.Data.GraphQL.Ast.Extensions
910

@@ -356,3 +357,123 @@ let ``Should generate information map correctly`` () =
356357
]
357358

358359
actual |> equals expected
360+
361+
[<Fact>]
362+
let ``ToQueryString escapes double quotes in string values`` () =
363+
let query = """query q { hero(name: "test\"quote") }"""
364+
let document = parse query
365+
let printed = document.ToQueryString()
366+
// Verify the printed query contains the escaped quote
367+
Assert.Contains("\\\"", printed)
368+
// Verify it can be parsed back
369+
let reparsed = parse printed
370+
equals (document.ToQueryString()) (reparsed.ToQueryString())
371+
372+
[<Fact>]
373+
let ``ToQueryString escapes backslashes in string values`` () =
374+
let query = """query q { hero(path: "C:\\Users\\test") }"""
375+
let document = parse query
376+
let printed = document.ToQueryString()
377+
// Verify the printed query contains escaped backslashes
378+
Assert.Contains("\\\\", printed)
379+
// Verify it can be parsed back
380+
let reparsed = parse printed
381+
equals (document.ToQueryString()) (reparsed.ToQueryString())
382+
383+
[<Fact>]
384+
let ``ToQueryString escapes newlines in string values`` () =
385+
let query = """query q { hero(text: "line1\nline2") }"""
386+
let document = parse query
387+
let printed = document.ToQueryString()
388+
// Verify the printed query contains the escaped newline within the string value
389+
Assert.Contains("\\n", printed)
390+
// Verify the string value itself doesn't contain an actual newline (it should be escaped)
391+
// The printed output will have formatting newlines, but the string value should have \n
392+
Assert.Contains("\"line1\\nline2\"", printed)
393+
// Verify it can be parsed back
394+
let reparsed = parse printed
395+
equals (document.ToQueryString()) (reparsed.ToQueryString())
396+
397+
[<Fact>]
398+
let ``ToQueryString escapes tabs in string values`` () =
399+
let query = """query q { hero(text: "col1\tcol2") }"""
400+
let document = parse query
401+
let printed = document.ToQueryString()
402+
// Verify the printed query contains the escaped tab within the string value
403+
Assert.Contains("\\t", printed)
404+
Assert.Contains("\"col1\\tcol2\"", printed)
405+
// Verify it can be parsed back
406+
let reparsed = parse printed
407+
equals (document.ToQueryString()) (reparsed.ToQueryString())
408+
409+
[<Fact>]
410+
let ``ToQueryString escapes carriage returns in string values`` () =
411+
let query = """query q { hero(text: "line1\rline2") }"""
412+
let document = parse query
413+
let printed = document.ToQueryString()
414+
// Verify the printed query contains the escaped carriage return
415+
Assert.Contains("\\r", printed)
416+
// Verify it can be parsed back
417+
let reparsed = parse printed
418+
equals (document.ToQueryString()) (reparsed.ToQueryString())
419+
420+
[<Fact>]
421+
let ``ToQueryString escapes backspace in string values`` () =
422+
let query = """query q { hero(text: "test\bback") }"""
423+
let document = parse query
424+
let printed = document.ToQueryString()
425+
// Verify the printed query contains the escaped backspace
426+
Assert.Contains("\\b", printed)
427+
// Verify it can be parsed back
428+
let reparsed = parse printed
429+
equals (document.ToQueryString()) (reparsed.ToQueryString())
430+
431+
[<Fact>]
432+
let ``ToQueryString escapes form feed in string values`` () =
433+
let query = """query q { hero(text: "page1\fpage2") }"""
434+
let document = parse query
435+
let printed = document.ToQueryString()
436+
// Verify the printed query contains the escaped form feed
437+
Assert.Contains("\\f", printed)
438+
// Verify it can be parsed back
439+
let reparsed = parse printed
440+
equals (document.ToQueryString()) (reparsed.ToQueryString())
441+
442+
[<Fact>]
443+
let ``ToQueryString escapes control characters as unicode in string values`` () =
444+
// Test with a control character (e.g., ASCII 0x01)
445+
let query = "query q { hero(text: \"test\u0001control\") }"
446+
let document = parse query
447+
let printed = document.ToQueryString()
448+
// Verify the printed query contains the unicode escape (lowercase hex)
449+
Assert.Contains("\\u0001", printed)
450+
// Verify it can be parsed back
451+
let reparsed = parse printed
452+
equals (document.ToQueryString()) (reparsed.ToQueryString())
453+
454+
[<Fact>]
455+
let ``ToQueryString escapes multiple special characters correctly`` () =
456+
let query = """query q { hero(text: "quote:\"newline:\nslash:\\tab:\t") }"""
457+
let document = parse query
458+
let printed = document.ToQueryString()
459+
// Verify all escapes are present
460+
Assert.Contains("\\\"", printed)
461+
Assert.Contains("\\n", printed)
462+
Assert.Contains("\\\\", printed)
463+
Assert.Contains("\\t", printed)
464+
// Verify it can be parsed back
465+
let reparsed = parse printed
466+
equals (document.ToQueryString()) (reparsed.ToQueryString())
467+
468+
[<Fact>]
469+
let ``ToQueryString produces deterministic output for escaped strings`` () =
470+
// This test verifies that the same query with escaped strings produces
471+
// the same canonical output, which is critical for documentId stability
472+
let query = """query Test { field(arg: "test\"quote\nline\ttab\\back") }"""
473+
let document = parse query
474+
let printed1 = document.ToQueryString()
475+
let printed2 = document.ToQueryString()
476+
equals printed1 printed2
477+
// Verify the documentId is deterministic
478+
let documentId = DocumentId.fromCanonicalQuery printed1
479+
equals 64 documentId.Length // SHA-256 hex string is always 64 chars

0 commit comments

Comments
 (0)