@@ -374,3 +374,180 @@ def test_roundtrip_with_scope():
374374 original = "junit:junit:jar:4.13.2:test"
375375 coord = Coordinate .parse (original )
376376 assert str (coord ) == original
377+
378+
379+ # =============================================================================
380+ # Strict mode tests (explicit positioning with empty strings)
381+ # =============================================================================
382+
383+
384+ def test_strict_mode_version_explicit ():
385+ """Test G:A:V: format - explicit version, no classifier (strict mode)."""
386+ result = Coordinate .parse ("org.example:my-artifact:1.2.3:" )
387+ assert result .groupId == "org.example"
388+ assert result .artifactId == "my-artifact"
389+ assert result .version == "1.2.3"
390+ assert result .classifier is None
391+ assert result .packaging is None
392+ assert result .scope is None
393+
394+
395+ def test_strict_mode_classifier_without_version ():
396+ """Test G:A::C format - no version, explicit classifier (strict mode)."""
397+ result = Coordinate .parse ("org.example:my-lib::sources" )
398+ assert result .groupId == "org.example"
399+ assert result .artifactId == "my-lib"
400+ assert result .version is None
401+ assert result .classifier == "sources"
402+ assert result .packaging is None
403+
404+
405+ def test_strict_mode_version_and_classifier ():
406+ """Test G:A:V:C: format - explicit version and classifier (strict mode)."""
407+ result = Coordinate .parse ("org.lwjgl:lwjgl:3.3.1:natives-linux:" )
408+ assert result .groupId == "org.lwjgl"
409+ assert result .artifactId == "lwjgl"
410+ assert result .version == "3.3.1"
411+ assert result .classifier == "natives-linux"
412+ assert result .packaging is None
413+ assert result .scope is None
414+
415+
416+ def test_strict_mode_version_skip_classifier_packaging ():
417+ """Test G:A:V::P format - version and packaging, skip classifier (strict mode)."""
418+ result = Coordinate .parse ("org.example:my-lib:1.0.0::pom" )
419+ assert result .groupId == "org.example"
420+ assert result .artifactId == "my-lib"
421+ assert result .version == "1.0.0"
422+ assert result .classifier is None
423+ assert result .packaging == "pom"
424+ assert result .scope is None
425+
426+
427+ def test_strict_mode_skip_version_packaging ():
428+ """Test G:A:::P format - skip version and classifier, specify packaging (strict mode)."""
429+ result = Coordinate .parse ("org.example:my-webapp:::pom" )
430+ assert result .groupId == "org.example"
431+ assert result .artifactId == "my-webapp"
432+ assert result .version is None
433+ assert result .classifier is None
434+ assert result .packaging == "pom"
435+
436+
437+ def test_strict_mode_full_with_scope ():
438+ """Test G:A:V:C:P:S format - full strict specification (strict mode)."""
439+ # Use empty classifier to trigger strict mode while specifying all positions
440+ result = Coordinate .parse ("org.example:my-lib:1.2.3::jar:compile" )
441+ assert result .groupId == "org.example"
442+ assert result .artifactId == "my-lib"
443+ assert result .version == "1.2.3"
444+ assert result .classifier is None
445+ assert result .packaging == "jar"
446+ assert result .scope == "compile"
447+
448+
449+ def test_strict_mode_only_scope ():
450+ """Test G:A::::S format - only scope specified (strict mode)."""
451+ result = Coordinate .parse ("org.example:my-lib::::test" )
452+ assert result .groupId == "org.example"
453+ assert result .artifactId == "my-lib"
454+ assert result .version is None
455+ assert result .classifier is None
456+ assert result .packaging is None
457+ assert result .scope == "test"
458+
459+
460+ def test_strict_mode_version_and_scope ():
461+ """Test G:A:V:::S format - version and scope, skip middle parts (strict mode)."""
462+ result = Coordinate .parse ("junit:junit:4.13.2:::test" )
463+ assert result .groupId == "junit"
464+ assert result .artifactId == "junit"
465+ assert result .version == "4.13.2"
466+ assert result .classifier is None
467+ assert result .packaging is None
468+ assert result .scope == "test"
469+
470+
471+ def test_strict_mode_invalid_scope ():
472+ """Test that strict mode validates scope values."""
473+ try :
474+ Coordinate .parse ("org.example:my-lib::::invalid-scope" )
475+ assert False , "Should have raised ValueError for invalid scope"
476+ except ValueError as e :
477+ assert "Invalid scope" in str (e )
478+ assert "invalid-scope" in str (e )
479+
480+
481+ def test_strict_mode_too_many_parts ():
482+ """Test that strict mode rejects more than 6 positions (7+ parts)."""
483+ try :
484+ # Need empty string to trigger strict mode, and 7+ parts
485+ Coordinate .parse ("g:a:v:c:p:s:extra:" )
486+ assert False , "Should have raised ValueError for too many parts"
487+ except ValueError as e :
488+ assert "Too many parts" in str (e )
489+ assert "at most G:A:V:C:P:S" in str (e )
490+
491+
492+ def test_strict_mode_disambiguate_version_vs_classifier ():
493+ """
494+ Test strict mode disambiguates ambiguous cases.
495+
496+ Without trailing colon, "sources" could be version or classifier (heuristic decides).
497+ With trailing colon, position is explicit.
498+ """
499+ # Heuristic mode: "sources" detected as classifier
500+ heuristic = Coordinate .parse ("org.example:my-lib:sources" )
501+ assert heuristic .classifier == "sources"
502+ assert heuristic .version is None
503+
504+ # Strict mode: "sources" in position 2 = version
505+ strict = Coordinate .parse ("org.example:my-lib:sources:" )
506+ assert strict .version == "sources" # Unusual but explicit
507+ assert strict .classifier is None
508+
509+
510+ def test_strict_mode_disambiguate_packaging_vs_version ():
511+ """
512+ Test strict mode disambiguates packaging vs version.
513+
514+ "jar" could be packaging (heuristic) or version (if used as version string).
515+ """
516+ # Heuristic mode: "jar" detected as packaging
517+ heuristic = Coordinate .parse ("org.example:my-lib:jar" )
518+ assert heuristic .packaging == "jar"
519+ assert heuristic .version is None
520+
521+ # Strict mode: "jar" in position 2 = version (unusual but explicit)
522+ strict = Coordinate .parse ("org.example:my-lib:jar:" )
523+ assert strict .version == "jar"
524+ assert strict .packaging is None
525+
526+
527+ def test_strict_mode_empty_version_string ():
528+ """Test G:A:: format - both version and classifier are explicitly empty."""
529+ result = Coordinate .parse ("org.example:my-lib::" )
530+ assert result .groupId == "org.example"
531+ assert result .artifactId == "my-lib"
532+ assert result .version is None
533+ assert result .classifier is None
534+ assert result .packaging is None
535+
536+
537+ def test_strict_mode_with_raw_flag ():
538+ """Test that strict mode works with raw flag (! suffix)."""
539+ result = Coordinate .parse ("org.example:my-lib:1.0.0:!" )
540+ assert result .groupId == "org.example"
541+ assert result .artifactId == "my-lib"
542+ assert result .version == "1.0.0"
543+ assert result .classifier is None
544+ assert result .raw is True
545+
546+
547+ def test_strict_mode_preserves_special_versions ():
548+ """Test that strict mode preserves RELEASE, LATEST, MANAGED versions."""
549+ result = Coordinate .parse ("org.example:my-lib:RELEASE:" )
550+ assert result .groupId == "org.example"
551+ assert result .artifactId == "my-lib"
552+ assert result .version == "RELEASE"
553+ assert result .classifier is None
0 commit comments