11# Copyright The IETF Trust 2026, All Rights Reserved
22import json
3- from io import BytesIO , StringIO
43from unittest import mock
54
5+ from django .core .files .base import ContentFile
66from django .core .files .storage import storages
77from django .test .utils import override_settings
88from lxml import etree
1313 create_rfc_txt_index ,
1414 create_rfc_xml_index ,
1515 format_rfc_number ,
16- save_to_red_bucket , get_unusable_rfc_numbers , get_april1_rfc_numbers ,
16+ save_to_red_bucket ,
17+ get_unusable_rfc_numbers ,
18+ get_april1_rfc_numbers ,
1719 get_publication_std_levels ,
1820)
1921from ietf .utils .test_utils import TestCase
@@ -39,7 +41,7 @@ def setUp(self):
3941 # Create an unused RFC number
4042 red_bucket .save (
4143 "input/unusable-rfc-numbers.json" ,
42- StringIO (json .dumps ([{"number" : 123 , "comment" : "" }])),
44+ ContentFile (json .dumps ([{"number" : 123 , "comment" : "" }])),
4345 )
4446
4547 # actual April 1 RFC
@@ -55,7 +57,7 @@ def setUp(self):
5557 # Set up a JSON file to flag the April 1 RFC
5658 red_bucket .save (
5759 "input/april-first-rfc-numbers.json" ,
58- StringIO (json .dumps ([self .april_fools_rfc .rfc_number ])),
60+ ContentFile (json .dumps ([self .april_fools_rfc .rfc_number ])),
5961 )
6062
6163 # non-April Fools RFC that happens to have been published on April 1
@@ -71,7 +73,7 @@ def setUp(self):
7173 # standard of self.rfc as different from its current value
7274 red_bucket .save (
7375 "input/publication-std-levels.json" ,
74- StringIO (
76+ ContentFile (
7577 json .dumps (
7678 [{"number" : self .rfc .rfc_number , "publication_std_level" : "ps" }]
7779 )
@@ -91,7 +93,8 @@ def test_create_rfc_txt_index(self, mock_save):
9193 create_rfc_txt_index ()
9294 self .assertEqual (mock_save .call_count , 1 )
9395 self .assertEqual (mock_save .call_args [0 ][0 ], "rfc-index.txt" )
94- contents = mock_save .call_args [0 ][1 ].read ()
96+ contents = mock_save .call_args [0 ][1 ]
97+ self .assertTrue (isinstance (contents , str ))
9598 self .assertIn (
9699 "123 Not Issued." ,
97100 contents ,
@@ -119,7 +122,8 @@ def test_create_rfc_xml_index(self, mock_save):
119122 create_rfc_xml_index ()
120123 self .assertEqual (mock_save .call_count , 1 )
121124 self .assertEqual (mock_save .call_args [0 ][0 ], "rfc-index.xml" )
122- contents = mock_save .call_args [0 ][1 ].read ()
125+ contents = mock_save .call_args [0 ][1 ]
126+ self .assertTrue (isinstance (contents , bytes ))
123127 ns = "{https://www.rfc-editor.org/rfc-index}" # NOT an f-string
124128 index = etree .fromstring (contents )
125129
@@ -190,21 +194,23 @@ def test_format_rfc_number(self):
190194 def test_save_to_red_bucket (self ):
191195 red_bucket = storages ["red_bucket" ]
192196 with override_settings (RFCINDEX_DELETE_THEN_WRITE = False ):
193- save_to_red_bucket ("test" , StringIO ("contents" ))
194- with red_bucket .open ("test" , "r" ) as f :
195- self .assertEqual (f .read (), "contents" )
197+ save_to_red_bucket ("test" , "contents \U0001f600 " )
198+ # Read as binary and explicitly decode to confirm encoding
199+ with red_bucket .open ("test" , "rb" ) as f :
200+ self .assertEqual (f .read ().decode ("utf-8" ), "contents \U0001f600 " )
196201 with override_settings (RFCINDEX_DELETE_THEN_WRITE = True ):
197- save_to_red_bucket ("test" , BytesIO (b"new contents" ))
198- with red_bucket .open ("test" , "r" ) as f :
199- self .assertEqual (f .read (), "new contents" )
202+ save_to_red_bucket ("test" , "new contents \U0001fae0 " .encode ("utf-8" ))
203+ # Read as binary and explicitly decode to confirm encoding
204+ with red_bucket .open ("test" , "rb" ) as f :
205+ self .assertEqual (f .read ().decode ("utf-8" ), "new contents \U0001fae0 " )
200206 red_bucket .delete ("test" ) # clean up like a good child
201207
202208 def test_get_unusable_rfc_numbers_raises (self ):
203209 """get_unusable_rfc_numbers should bail on errors"""
204210 with self .assertRaises (FileNotFoundError ):
205211 get_unusable_rfc_numbers ()
206212 red_bucket = storages ["red_bucket" ]
207- red_bucket .save ("unusable-rfc-numbers.json" , StringIO ("not json" ))
213+ red_bucket .save ("unusable-rfc-numbers.json" , ContentFile ("not json" ))
208214 with self .assertRaises (json .JSONDecodeError ):
209215 get_unusable_rfc_numbers ()
210216 red_bucket .delete ("unusable-rfc-numbers.json" )
@@ -214,7 +220,7 @@ def test_get_april1_rfc_numbers_raises(self):
214220 with self .assertRaises (FileNotFoundError ):
215221 get_april1_rfc_numbers ()
216222 red_bucket = storages ["red_bucket" ]
217- red_bucket .save ("april-first-rfc-numbers.json" , StringIO ("not json" ))
223+ red_bucket .save ("april-first-rfc-numbers.json" , ContentFile ("not json" ))
218224 with self .assertRaises (json .JSONDecodeError ):
219225 get_april1_rfc_numbers ()
220226 red_bucket .delete ("april-first-rfc-numbers.json" )
@@ -224,7 +230,7 @@ def test_get_publication_std_levels_raises(self):
224230 with self .assertRaises (FileNotFoundError ):
225231 get_publication_std_levels ()
226232 red_bucket = storages ["red_bucket" ]
227- red_bucket .save ("publication-std-levels.json" , StringIO ("not json" ))
233+ red_bucket .save ("publication-std-levels.json" , ContentFile ("not json" ))
228234 with self .assertRaises (json .JSONDecodeError ):
229235 get_publication_std_levels ()
230236 red_bucket .delete ("publication-std-levels.json" )
0 commit comments