@@ -34,10 +34,22 @@ def test_missing_required_attributes() -> None:
3434 expected_errors = {
3535 "source" : [
3636 str (MissingRequiredAttributeError ("source" )),
37+ str (
38+ InvalidAttributeValueError (
39+ attribute_name = "source" ,
40+ msg = "Attribute 'source' must not be None or empty" ,
41+ )
42+ ),
3743 str (InvalidAttributeTypeError ("source" , str )),
3844 ],
3945 "type" : [
4046 str (MissingRequiredAttributeError ("type" )),
47+ str (
48+ InvalidAttributeValueError (
49+ attribute_name = "type" ,
50+ msg = "Attribute 'type' must not be None or empty" ,
51+ )
52+ ),
4153 str (InvalidAttributeTypeError ("type" , str )),
4254 ],
4355 }
@@ -277,40 +289,139 @@ def test_schemaurl_validation(schemaurl: Any, expected_error: dict) -> None:
277289
278290
279291@pytest .mark .parametrize (
280- "extension_name,expected_error " ,
292+ "attributes,expected_errors " ,
281293 [
282294 (
283- "" ,
295+ { "id" : "" , "source" : "/" , "type" : "test" } ,
284296 {
285- "" : [
297+ "id " : [
286298 str (
287- CustomExtensionAttributeError (
288- "" ,
289- "Extension attribute '' should be between 1 and 20 characters long" ,
299+ InvalidAttributeValueError (
300+ attribute_name = "id" ,
301+ msg = "Attribute 'id' must not be None or empty" ,
302+ )
303+ )
304+ ]
305+ },
306+ ),
307+ (
308+ {"id" : None , "source" : "/" , "type" : "test" },
309+ {
310+ "id" : [
311+ str (
312+ InvalidAttributeValueError (
313+ attribute_name = "id" ,
314+ msg = "Attribute 'id' must not be None or empty" ,
290315 )
291316 ),
292317 str (
293- CustomExtensionAttributeError (
294- "" ,
295- "Extension attribute '' should only contain lowercase letters and numbers" ,
318+ InvalidAttributeTypeError (
319+ attribute_name = "id" , expected_type = str
296320 )
297321 ),
298322 ]
299323 },
300324 ),
301325 (
302- "thisisaverylongextension" ,
326+ { "id" : "1" , "source" : "" , "type" : "test" } ,
303327 {
304- "thisisaverylongextension " : [
328+ "source " : [
305329 str (
306- CustomExtensionAttributeError (
307- "thisisaverylongextension" ,
308- "Extension attribute 'thisisaverylongextension' should be between 1 and 20 characters long" ,
330+ InvalidAttributeValueError (
331+ attribute_name = "source" ,
332+ msg = "Attribute 'source' must not be None or empty" ,
333+ )
334+ )
335+ ]
336+ },
337+ ),
338+ (
339+ {"id" : "1" , "source" : None , "type" : "test" },
340+ {
341+ "source" : [
342+ str (
343+ InvalidAttributeValueError (
344+ attribute_name = "source" ,
345+ msg = "Attribute 'source' must not be None or empty" ,
346+ )
347+ ),
348+ str (
349+ InvalidAttributeTypeError (
350+ attribute_name = "source" , expected_type = str
351+ )
352+ ),
353+ ]
354+ },
355+ ),
356+ (
357+ {"id" : "1" , "source" : "/" , "type" : "" },
358+ {
359+ "type" : [
360+ str (
361+ InvalidAttributeValueError (
362+ attribute_name = "type" ,
363+ msg = "Attribute 'type' must not be None or empty" ,
309364 )
310365 )
311366 ]
312367 },
313368 ),
369+ (
370+ {"id" : "1" , "source" : "/" , "type" : None },
371+ {
372+ "type" : [
373+ str (
374+ InvalidAttributeValueError (
375+ attribute_name = "type" ,
376+ msg = "Attribute 'type' must not be None or empty" ,
377+ )
378+ ),
379+ str (
380+ InvalidAttributeTypeError (
381+ attribute_name = "type" , expected_type = str
382+ )
383+ ),
384+ ]
385+ },
386+ ),
387+ ],
388+ )
389+ def test_required_attributes_null_or_empty (
390+ attributes : dict [str , Any ], expected_errors : dict
391+ ) -> None :
392+ with pytest .raises (CloudEventValidationError ) as e :
393+ CloudEvent (attributes = attributes )
394+
395+ actual_errors = {
396+ key : [str (e ) for e in value ] for key , value in e .value .errors .items ()
397+ }
398+ for key , expected_msgs in expected_errors .items ():
399+ assert key in actual_errors
400+ assert actual_errors [key ] == expected_msgs
401+
402+
403+ @pytest .mark .parametrize (
404+ "extension_name,expected_error" ,
405+ [
406+ (
407+ "" ,
408+ {
409+ "" : [
410+ str (
411+ CustomExtensionAttributeError (
412+ "" ,
413+ "Extension attribute name must be at least 1 character long but was ''" ,
414+ )
415+ ),
416+ str (
417+ CustomExtensionAttributeError (
418+ "" ,
419+ "Extension attribute '' should only contain lowercase letters and numbers" ,
420+ )
421+ ),
422+ ]
423+ },
424+ ),
314425 (
315426 "data" ,
316427 {
@@ -344,6 +455,21 @@ def test_custom_extension(extension_name: str, expected_error: dict) -> None:
344455 assert actual_errors == expected_error
345456
346457
458+ def test_long_extension_attribute_name () -> None :
459+ # Verify that extension attribute names longer than 20 characters are allowed
460+ long_name = "a" * 21
461+ event = CloudEvent (
462+ {
463+ "id" : "1" ,
464+ "source" : "/" ,
465+ "type" : "test" ,
466+ "specversion" : "0.3" ,
467+ long_name : "value" ,
468+ }
469+ )
470+ assert event .get_extension (long_name ) == "value"
471+
472+
347473def test_default_specversion () -> None :
348474 event = CloudEvent (
349475 attributes = {"source" : "/source" , "type" : "test" , "id" : "1" },
0 commit comments