@@ -28,6 +28,8 @@ import {
28
28
readBinaryFile ,
29
29
getAccessTime ,
30
30
getFileSize ,
31
+ readJsonFileAsStream ,
32
+ writeJsonFileAsStream ,
31
33
} from "../src/fs.js" ;
32
34
33
35
import { useTmpDir } from "./helpers/fs.js" ;
@@ -464,6 +466,137 @@ describe("File system utils", () => {
464
466
} ) ;
465
467
} ) ;
466
468
469
+ describe ( "readJsonFileAsStream" , ( ) => {
470
+ it ( "Should read and parse a JSON file" , async ( ) => {
471
+ const expectedObject = { a : 1 , b : 2 } ;
472
+ const filePath = path . join ( getTmpDir ( ) , "file.json" ) ;
473
+ await writeUtf8File ( filePath , JSON . stringify ( expectedObject ) ) ;
474
+
475
+ assert . deepEqual ( await readJsonFileAsStream ( filePath ) , expectedObject ) ;
476
+ expectTypeOf ( await readJsonFileAsStream ( filePath ) ) . toBeUnknown ( ) ;
477
+ expectTypeOf (
478
+ await readJsonFileAsStream < { a : number ; b : number } > ( filePath ) ,
479
+ ) . toMatchTypeOf < { a : number ; b : number } > ( ) ;
480
+ } ) ;
481
+
482
+ it ( "Should throw InvalidFileFormatError if the file is not valid JSON" , async ( ) => {
483
+ const filePath = path . join ( getTmpDir ( ) , "file.json" ) ;
484
+ await writeUtf8File ( filePath , "not-json" ) ;
485
+
486
+ await assert . rejects ( readJsonFileAsStream ( filePath ) , {
487
+ name : "InvalidFileFormatError" ,
488
+ message : `Invalid file format: ${ filePath } ` ,
489
+ } ) ;
490
+ } ) ;
491
+
492
+ it ( "Should throw InvalidFileFormatError if the file is empty" , async ( ) => {
493
+ const filePath = path . join ( getTmpDir ( ) , "file.json" ) ;
494
+ await writeUtf8File ( filePath , "" ) ;
495
+
496
+ await assert . rejects ( readJsonFileAsStream ( filePath ) , {
497
+ name : "InvalidFileFormatError" ,
498
+ message : `Invalid file format: ${ filePath } ` ,
499
+ } ) ;
500
+ } ) ;
501
+
502
+ it ( "Should throw FileNotFoundError if the file doesn't exist" , async ( ) => {
503
+ const filePath = path . join ( getTmpDir ( ) , "not-exists.json" ) ;
504
+
505
+ await assert . rejects ( readJsonFileAsStream ( filePath ) , {
506
+ name : "FileNotFoundError" ,
507
+ message : `File ${ filePath } not found` ,
508
+ } ) ;
509
+ } ) ;
510
+
511
+ it ( "Should throw IsDirectoryError if the file is a directory" , async ( ) => {
512
+ const filePath = path . join ( getTmpDir ( ) ) ;
513
+
514
+ await assert . rejects ( readJsonFileAsStream ( filePath ) , {
515
+ name : "IsDirectoryError" ,
516
+ message : `Path ${ filePath } is a directory` ,
517
+ } ) ;
518
+ } ) ;
519
+
520
+ it ( "Should throw FileSystemAccessError if a different error is thrown" , async ( ) => {
521
+ const invalidPath = "\0" ;
522
+
523
+ await assert . rejects ( readJsonFileAsStream ( invalidPath ) , {
524
+ name : "FileSystemAccessError" ,
525
+ } ) ;
526
+ } ) ;
527
+ } ) ;
528
+
529
+ describe ( "writeJsonFileAsStream" , ( ) => {
530
+ it ( "Should write an object to a JSON file" , async ( ) => {
531
+ const expectedObject = { a : 1 , b : 2 } ;
532
+ const filePath = path . join ( getTmpDir ( ) , "file.json" ) ;
533
+
534
+ await writeJsonFileAsStream ( filePath , expectedObject ) ;
535
+
536
+ assert . deepEqual (
537
+ JSON . parse ( await readUtf8File ( filePath ) ) ,
538
+ expectedObject ,
539
+ ) ;
540
+ expectTypeOf (
541
+ writeJsonFile < { a : number ; b : number } > ( filePath , expectedObject ) ,
542
+ ) ;
543
+ } ) ;
544
+
545
+ it ( "Should write an object tto a JSON file even if part of the path doesn't exist" , async ( ) => {
546
+ const expectedObject = { a : 1 , b : 2 } ;
547
+ const filePath = path . join ( getTmpDir ( ) , "not-exists" , "file.json" ) ;
548
+
549
+ await writeJsonFileAsStream ( filePath , expectedObject ) ;
550
+
551
+ assert . deepEqual (
552
+ JSON . parse ( await readUtf8File ( filePath ) ) ,
553
+ expectedObject ,
554
+ ) ;
555
+ } ) ;
556
+
557
+ it ( "Should throw JsonSerializationError if the object can't be serialized to JSON" , async ( ) => {
558
+ const filePath = path . join ( getTmpDir ( ) , "file.json" ) ;
559
+ // create an object with a circular reference
560
+ const circularObject : { self ?: { } } = { } ;
561
+ circularObject . self = circularObject ;
562
+
563
+ await assert . rejects ( writeJsonFileAsStream ( filePath , circularObject ) , {
564
+ name : "JsonSerializationError" ,
565
+ message : `Error serializing JSON file ${ filePath } ` ,
566
+ } ) ;
567
+ } ) ;
568
+
569
+ it ( "Should throw FileSystemAccessError if a different error is thrown" , async ( ) => {
570
+ const filePath = path . join ( getTmpDir ( ) , "protected-file.json" ) ;
571
+ await createFile ( filePath ) ;
572
+
573
+ try {
574
+ await chmod ( filePath , 0o444 ) ;
575
+
576
+ await assert . rejects ( writeJsonFileAsStream ( filePath , { } ) , {
577
+ name : "FileSystemAccessError" ,
578
+ } ) ;
579
+ } finally {
580
+ await chmod ( filePath , 0o666 ) ;
581
+ }
582
+ } ) ;
583
+
584
+ it ( "Should remove the part of the path that didn't exist before if an error is thrown" , async ( ) => {
585
+ const dirPath = path . join ( getTmpDir ( ) , "not-exists" ) ;
586
+ const filePath = path . join ( dirPath , "protected-file.json" ) ;
587
+ // create an object with a circular reference
588
+ const circularObject : { self ?: { } } = { } ;
589
+ circularObject . self = circularObject ;
590
+
591
+ await assert . rejects ( writeJsonFileAsStream ( filePath , circularObject ) , {
592
+ name : "JsonSerializationError" ,
593
+ message : `Error serializing JSON file ${ filePath } ` ,
594
+ } ) ;
595
+
596
+ assert . ok ( ! ( await exists ( dirPath ) ) , "The directory should not exist" ) ;
597
+ } ) ;
598
+ } ) ;
599
+
467
600
describe ( "readUtf8File" , ( ) => {
468
601
it ( "Should read a file and return its content as a string" , async ( ) => {
469
602
const content = "hello" ;
0 commit comments