Skip to content

Commit 5f4773c

Browse files
authored
Merge pull request #1549 from fsprojects/daily-test-improver/csv-extensions-tests
Daily Test Coverage Improver: Add CSV functionality tests
2 parents 2e2908f + 02f7d33 commit 5f4773c

2 files changed

Lines changed: 266 additions & 0 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
// --------------------------------------------------------------------------------------
2+
// Tests for the CsvFile and CsvRow functionality
3+
// --------------------------------------------------------------------------------------
4+
5+
module FSharp.Data.Tests.CsvFile
6+
7+
open NUnit.Framework
8+
open FsUnit
9+
open System.IO
10+
open FSharp.Data
11+
open FSharp.Data.CsvExtensions
12+
13+
// Sample CSV data for testing
14+
let sampleCsvData = """Name,Age,City
15+
John,25,New York
16+
Jane,30,London
17+
Bob,35,Paris"""
18+
19+
let sampleCsvNoHeaders = """John,25,New York
20+
Jane,30,London
21+
Bob,35,Paris"""
22+
23+
let sampleCsvWithQuotes = """Name,Description,Score
24+
Alice,"Software Engineer, Senior",95
25+
Bob,"Product Manager""s Assistant",88
26+
Charlie,"Data Scientist
27+
with ML focus",92"""
28+
29+
[<Test>]
30+
let ``CsvFile.Parse works with headers`` () =
31+
let csv = CsvFile.Parse(sampleCsvData)
32+
33+
csv.Headers |> should equal (Some [| "Name"; "Age"; "City" |])
34+
csv.NumberOfColumns |> should equal 3
35+
let rows = csv.Rows |> Array.ofSeq
36+
rows.Length |> should equal 3
37+
rows.[0].Columns |> should equal [| "John"; "25"; "New York" |]
38+
39+
[<Test>]
40+
let ``CsvFile.Parse works without headers`` () =
41+
let csv = CsvFile.Parse(sampleCsvNoHeaders, hasHeaders=false)
42+
43+
csv.Headers |> should equal None
44+
csv.NumberOfColumns |> should equal 3
45+
let rows = csv.Rows |> Array.ofSeq
46+
rows.Length |> should equal 3
47+
rows.[0].Columns |> should equal [| "John"; "25"; "New York" |]
48+
49+
[<Test>]
50+
let ``CsvFile.Parse handles custom separators`` () =
51+
let csvData = "Name;Age;City\nJohn;25;New York"
52+
let csv = CsvFile.Parse(csvData, separators=";")
53+
54+
csv.Headers |> should equal (Some [| "Name"; "Age"; "City" |])
55+
let row = csv.Rows |> Seq.head
56+
row.Columns |> should equal [| "John"; "25"; "New York" |]
57+
58+
[<Test>]
59+
let ``CsvFile.Parse handles custom quotes`` () =
60+
let csvData = "Name,Description\nAlice,'Software Engineer, Senior'"
61+
let csv = CsvFile.Parse(csvData, quote=''')
62+
63+
let row = csv.Rows |> Seq.head
64+
row.Columns |> should equal [| "Alice"; "Software Engineer, Senior" |]
65+
66+
[<Test>]
67+
let ``CsvFile.Parse handles skipRows`` () =
68+
let csvData = """Comment line 1
69+
Comment line 2
70+
Name,Age
71+
John,25
72+
Jane,30"""
73+
let csv = CsvFile.Parse(csvData, skipRows=2)
74+
75+
csv.Headers |> should equal (Some [| "Name"; "Age" |])
76+
let rows = csv.Rows |> Array.ofSeq
77+
rows.Length |> should equal 2
78+
rows.[0].Columns |> should equal [| "John"; "25" |]
79+
80+
[<Test>]
81+
let ``CsvFile.Parse handles ignoreErrors`` () =
82+
let csvData = """Name,Age,City
83+
John,25,New York
84+
Jane,30 // This row has only 2 columns
85+
Bob,35,Paris"""
86+
let csv = CsvFile.Parse(csvData, ignoreErrors=true)
87+
88+
let rows = csv.Rows |> Array.ofSeq
89+
// Should ignore the malformed row
90+
rows.Length |> should equal 2
91+
rows.[0].Columns |> should equal [| "John"; "25"; "New York" |]
92+
rows.[1].Columns |> should equal [| "Bob"; "35"; "Paris" |]
93+
94+
[<Test>]
95+
let ``CsvFile.Load from stream works`` () =
96+
use stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(sampleCsvData))
97+
let csv = CsvFile.Load(stream)
98+
99+
csv.Headers |> should equal (Some [| "Name"; "Age"; "City" |])
100+
let rows = csv.Rows |> Array.ofSeq
101+
rows.Length |> should equal 3
102+
103+
[<Test>]
104+
let ``CsvFile.Load from TextReader works`` () =
105+
use reader = new StringReader(sampleCsvData)
106+
let csv = CsvFile.Load(reader)
107+
108+
csv.Headers |> should equal (Some [| "Name"; "Age"; "City" |])
109+
let rows = csv.Rows |> Array.ofSeq
110+
rows.Length |> should equal 3
111+
112+
[<Test>]
113+
let ``GetColumnIndex returns correct index for valid column`` () =
114+
let csv = CsvFile.Parse(sampleCsvData)
115+
116+
csv.GetColumnIndex("Name") |> should equal 0
117+
csv.GetColumnIndex("Age") |> should equal 1
118+
csv.GetColumnIndex("City") |> should equal 2
119+
120+
[<Test>]
121+
let ``GetColumnIndex throws for invalid column`` () =
122+
let csv = CsvFile.Parse(sampleCsvData)
123+
124+
Assert.Throws<System.Collections.Generic.KeyNotFoundException>(fun () -> csv.GetColumnIndex("InvalidColumn") |> ignore) |> ignore
125+
126+
[<Test>]
127+
let ``TryGetColumnIndex returns Some for valid column`` () =
128+
let csv = CsvFile.Parse(sampleCsvData)
129+
130+
csv.TryGetColumnIndex("Name") |> should equal (Some 0)
131+
csv.TryGetColumnIndex("Age") |> should equal (Some 1)
132+
csv.TryGetColumnIndex("City") |> should equal (Some 2)
133+
134+
[<Test>]
135+
let ``TryGetColumnIndex returns None for invalid column`` () =
136+
let csv = CsvFile.Parse(sampleCsvData)
137+
138+
csv.TryGetColumnIndex("InvalidColumn") |> should equal None
139+
140+
[<Test>]
141+
let ``CsvRow GetColumn by index works`` () =
142+
let csv = CsvFile.Parse(sampleCsvData)
143+
let row = csv.Rows |> Seq.head
144+
145+
row.GetColumn(0) |> should equal "John"
146+
row.GetColumn(1) |> should equal "25"
147+
row.GetColumn(2) |> should equal "New York"
148+
149+
[<Test>]
150+
let ``CsvRow GetColumn by name works`` () =
151+
let csv = CsvFile.Parse(sampleCsvData)
152+
let row = csv.Rows |> Seq.head
153+
154+
row.GetColumn("Name") |> should equal "John"
155+
row.GetColumn("Age") |> should equal "25"
156+
row.GetColumn("City") |> should equal "New York"
157+
158+
[<Test>]
159+
let ``CsvRow indexer by int works`` () =
160+
let csv = CsvFile.Parse(sampleCsvData)
161+
let row = csv.Rows |> Seq.head
162+
163+
row.[0] |> should equal "John"
164+
row.[1] |> should equal "25"
165+
row.[2] |> should equal "New York"
166+
167+
[<Test>]
168+
let ``CsvRow indexer by string works`` () =
169+
let csv = CsvFile.Parse(sampleCsvData)
170+
let row = csv.Rows |> Seq.head
171+
172+
row.["Name"] |> should equal "John"
173+
row.["Age"] |> should equal "25"
174+
row.["City"] |> should equal "New York"
175+
176+
[<Test>]
177+
let ``CsvRow dynamic operator works`` () =
178+
let csv = CsvFile.Parse(sampleCsvData)
179+
let row = csv.Rows |> Seq.head
180+
181+
row?Name |> should equal "John"
182+
row?Age |> should equal "25"
183+
row?City |> should equal "New York"
184+
185+
// Tests for StringExtensions
186+
[<Test>]
187+
let ``StringExtensions.AsInteger works with valid input`` () =
188+
"123".AsInteger() |> should equal 123
189+
"-456".AsInteger() |> should equal -456
190+
191+
[<Test>]
192+
let ``StringExtensions.AsInteger throws with invalid input`` () =
193+
Assert.Throws<System.Exception>(fun () -> "not a number".AsInteger() |> ignore) |> ignore
194+
195+
[<Test>]
196+
let ``StringExtensions.AsInteger64 works with valid input`` () =
197+
"123456789012345".AsInteger64() |> should equal 123456789012345L
198+
"-987654321098765".AsInteger64() |> should equal -987654321098765L
199+
200+
[<Test>]
201+
let ``StringExtensions.AsInteger64 throws with invalid input`` () =
202+
Assert.Throws<System.Exception>(fun () -> "not a number".AsInteger64() |> ignore) |> ignore
203+
204+
[<Test>]
205+
let ``StringExtensions.AsDecimal works with valid input`` () =
206+
"123.45".AsDecimal() |> should equal 123.45m
207+
"-67.89".AsDecimal() |> should equal -67.89m
208+
209+
[<Test>]
210+
let ``StringExtensions.AsDecimal throws with invalid input`` () =
211+
Assert.Throws<System.Exception>(fun () -> "not a decimal".AsDecimal() |> ignore) |> ignore
212+
213+
[<Test>]
214+
let ``StringExtensions.AsFloat works with valid input`` () =
215+
"123.45".AsFloat() |> should equal 123.45
216+
"-67.89".AsFloat() |> should equal -67.89
217+
218+
[<Test>]
219+
let ``StringExtensions.AsFloat throws with invalid input`` () =
220+
Assert.Throws<System.Exception>(fun () -> "not a float".AsFloat() |> ignore) |> ignore
221+
222+
[<Test>]
223+
let ``StringExtensions.AsBoolean works with valid input`` () =
224+
"true".AsBoolean() |> should equal true
225+
"false".AsBoolean() |> should equal false
226+
"True".AsBoolean() |> should equal true
227+
"False".AsBoolean() |> should equal false
228+
"1".AsBoolean() |> should equal true
229+
"0".AsBoolean() |> should equal false
230+
231+
[<Test>]
232+
let ``StringExtensions.AsBoolean throws with invalid input`` () =
233+
Assert.Throws<System.Exception>(fun () -> "not a boolean".AsBoolean() |> ignore) |> ignore
234+
235+
[<Test>]
236+
let ``StringExtensions.AsDateTime works with valid input`` () =
237+
let result = "2023-12-25".AsDateTime()
238+
result.Year |> should equal 2023
239+
result.Month |> should equal 12
240+
result.Day |> should equal 25
241+
242+
[<Test>]
243+
let ``StringExtensions.AsDateTime throws with invalid input`` () =
244+
Assert.Throws<System.Exception>(fun () -> "not a date".AsDateTime() |> ignore) |> ignore
245+
246+
[<Test>]
247+
let ``StringExtensions.AsGuid works with valid input`` () =
248+
let guidString = "12345678-1234-1234-1234-123456789abc"
249+
let result = guidString.AsGuid()
250+
result.ToString() |> should equal guidString
251+
252+
[<Test>]
253+
let ``StringExtensions.AsGuid throws with invalid input`` () =
254+
Assert.Throws<System.Exception>(fun () -> "not a guid".AsGuid() |> ignore) |> ignore
255+
256+
[<Test>]
257+
let ``Complex CSV with quotes and newlines handled correctly`` () =
258+
let csv = CsvFile.Parse(sampleCsvWithQuotes)
259+
let rows = csv.Rows |> Array.ofSeq
260+
261+
rows.Length |> should equal 3
262+
rows.[0].["Name"] |> should equal "Alice"
263+
rows.[0].["Description"] |> should equal "Software Engineer, Senior"
264+
rows.[1].["Description"] |> should equal "Product Manager\"s Assistant"
265+
rows.[2].["Description"] |> should equal "Data Scientist\nwith ML focus"

tests/FSharp.Data.Core.Tests/FSharp.Data.Core.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="JsonParserProperties.fs" />
2626
<Compile Include="JsonConversions.fs" />
2727
<Compile Include="CsvReader.fs" />
28+
<Compile Include="CsvFile.fs" />
2829
<Compile Include="HtmlCharRefs.fs" />
2930
<Compile Include="HtmlParser.fs" />
3031
<Compile Include="HtmlOperations.fs" />

0 commit comments

Comments
 (0)