tldts-tests 7.2.1 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -731,6 +731,264 @@ export default function test(
731
731
  });
732
732
  });
733
733
 
734
+ // The default (validating) path fuses host validation into the extraction
735
+ // scan: for a "simple" authority (no userinfo / port / brackets / trailing
736
+ // dot / control char) parseImpl skips the separate isValidHostname pass and
737
+ // trusts the verdict accumulated during extraction. These cases pin the
738
+ // user-facing contract of that fusion — exercised end-to-end through the
739
+ // public API — at the structural boundaries (label/total length, hyphen, dot,
740
+ // underscore positions) and across the simple-vs-complex authority split. All
741
+ // expected values were cross-checked against the v7.2.0 pre-fusion baseline
742
+ // (0 divergences), so an accidental behaviour change here is a regression.
743
+ describe('inline hostname validation (extract/validate fusion)', () => {
744
+ const rep = (s: string, n: number): string => {
745
+ let r = '';
746
+ for (let i = 0; i < n; i += 1) {
747
+ r += s;
748
+ }
749
+ return r;
750
+ };
751
+
752
+ describe('label-length boundary (63 valid / 64 invalid)', () => {
753
+ it('accepts a 63-char first label, rejects 64', () => {
754
+ expect(tldts.getHostname('http://' + rep('a', 63) + '.com/p')).to.equal(
755
+ rep('a', 63) + '.com',
756
+ );
757
+ expect(tldts.getHostname('http://' + rep('a', 64) + '.com/p')).to.equal(
758
+ null,
759
+ );
760
+ });
761
+
762
+ it('accepts a 63-char last label, rejects 64', () => {
763
+ expect(tldts.getHostname('http://ok.' + rep('a', 63))).to.equal(
764
+ 'ok.' + rep('a', 63),
765
+ );
766
+ expect(tldts.getHostname('http://ok.' + rep('a', 64))).to.equal(null);
767
+ });
768
+
769
+ it('accepts a single 63-char label, rejects 64 (no dots)', () => {
770
+ // Public suffix of a bare single label is the label itself => no domain,
771
+ // but the hostname must still be extracted (or rejected at 64).
772
+ expect(tldts.getHostname('http://' + rep('a', 63) + '/p')).to.equal(
773
+ rep('a', 63),
774
+ );
775
+ expect(tldts.getHostname('http://' + rep('a', 64) + '/p')).to.equal(
776
+ null,
777
+ );
778
+ });
779
+
780
+ it('agrees between URL and bare-hostname forms at the boundary', () => {
781
+ // The bare form takes the urlIsValidHostname reference path; the URL
782
+ // form takes the inline-validation path. Both must converge.
783
+ expect(tldts.getHostname(rep('a', 63) + '.com')).to.equal(
784
+ rep('a', 63) + '.com',
785
+ );
786
+ expect(tldts.getHostname(rep('a', 64) + '.com')).to.equal(null);
787
+ });
788
+ });
789
+
790
+ describe('total-length boundary (255 valid / 256 invalid)', () => {
791
+ // 'aaa.' * 63 + 'aaa' = 255 chars with every label <= 63, so this isolates
792
+ // the total-length guard from the per-label guard. +1 char = 256.
793
+ const h255 = rep('aaa.', 63) + 'aaa';
794
+ const h256 = rep('aaa.', 63) + 'aaaa';
795
+
796
+ it('precondition: lengths are exactly 255 and 256', () => {
797
+ expect(h255.length).to.equal(255);
798
+ expect(h256.length).to.equal(256);
799
+ });
800
+
801
+ it('accepts a 255-char host, rejects 256 (URL form)', () => {
802
+ expect(tldts.getHostname('http://' + h255 + '/p')).to.equal(h255);
803
+ expect(tldts.getHostname('http://' + h256 + '/p')).to.equal(null);
804
+ });
805
+
806
+ it('accepts a 255-char host, rejects 256 (bare form)', () => {
807
+ expect(tldts.getHostname(h255)).to.equal(h255);
808
+ expect(tldts.getHostname(h256)).to.equal(null);
809
+ });
810
+ });
811
+
812
+ describe('dot placement', () => {
813
+ it('accepts a single leading dot', () => {
814
+ expect(tldts.getHostname('http://.example.com/p')).to.equal(
815
+ '.example.com',
816
+ );
817
+ });
818
+
819
+ it('strips a single and multiple trailing dots', () => {
820
+ expect(tldts.getHostname('http://example.com./p')).to.equal(
821
+ 'example.com',
822
+ );
823
+ expect(tldts.getHostname('http://example.com.../p')).to.equal(
824
+ 'example.com',
825
+ );
826
+ });
827
+
828
+ it('rejects consecutive dots (empty label)', () => {
829
+ expect(tldts.getHostname('http://a..b.com/p')).to.equal(null);
830
+ expect(tldts.getHostname('a..b.com')).to.equal(null);
831
+ });
832
+ });
833
+
834
+ describe('hyphen placement', () => {
835
+ it('rejects a leading hyphen on the host', () => {
836
+ expect(tldts.getHostname('http://-example.com/p')).to.equal(null);
837
+ expect(tldts.getHostname('-example.com')).to.equal(null);
838
+ });
839
+
840
+ it('rejects a trailing hyphen on the whole host', () => {
841
+ expect(tldts.getHostname('http://example.com-/p')).to.equal(null);
842
+ expect(tldts.getHostname('example.com-')).to.equal(null);
843
+ });
844
+
845
+ it('rejects a label that ends with a hyphen', () => {
846
+ expect(tldts.getHostname('http://foo-.bar.com/p')).to.equal(null);
847
+ });
848
+
849
+ it('accepts a hyphen mid-label', () => {
850
+ expect(tldts.getHostname('http://ex-ample.com/p')).to.equal(
851
+ 'ex-ample.com',
852
+ );
853
+ expect(tldts.getHostname('ex-ample.com')).to.equal('ex-ample.com');
854
+ });
855
+ });
856
+
857
+ describe('underscore placement (DNS-lenient, RFC 2181 §11)', () => {
858
+ it('accepts a leading underscore', () => {
859
+ expect(tldts.getHostname('http://_example.com/p')).to.equal(
860
+ '_example.com',
861
+ );
862
+ });
863
+
864
+ it('accepts a mid-label underscore', () => {
865
+ expect(tldts.getHostname('http://ex_ample.com/p')).to.equal(
866
+ 'ex_ample.com',
867
+ );
868
+ });
869
+
870
+ it('accepts a label-final underscore', () => {
871
+ expect(tldts.getHostname('http://spf_.example.com/p')).to.equal(
872
+ 'spf_.example.com',
873
+ );
874
+ });
875
+ });
876
+
877
+ describe('character classes', () => {
878
+ it('lower-cases an uppercase host then validates it (URL and bare)', () => {
879
+ expect(tldts.getHostname('HTTP://SUB.EXAMPLE.COM/P')).to.equal(
880
+ 'sub.example.com',
881
+ );
882
+ expect(tldts.getHostname('EXAMPLE.COM')).to.equal('example.com');
883
+ });
884
+
885
+ it('keeps a non-ASCII / IDN host without punycode (URL and bare)', () => {
886
+ expect(tldts.getHostname('http://bücher.example/p')).to.equal(
887
+ 'bücher.example',
888
+ );
889
+ expect(tldts.getHostname('ящик-с-апельсинами.рф')).to.equal(
890
+ 'ящик-с-апельсинами.рф',
891
+ );
892
+ });
893
+
894
+ it('accepts a digits-only host (no IPv4 normalisation)', () => {
895
+ expect(tldts.getHostname('http://123.456/p')).to.equal('123.456');
896
+ expect(tldts.getHostname('123.456')).to.equal('123.456');
897
+ });
898
+
899
+ it('rejects a forbidden host character (%, space)', () => {
900
+ expect(tldts.getHostname('http://a%b.com/p')).to.equal(null);
901
+ expect(tldts.getHostname('http://a b.com/p')).to.equal(null);
902
+ });
903
+ });
904
+
905
+ describe('simple-vs-complex authority split (the fusion gate)', () => {
906
+ // The fusion is ACTIVE only for a simple authority; a complex one
907
+ // (userinfo / port / brackets / trailing dot) falls back to the separate
908
+ // isValidHostname pass. Both branches must yield the identical host.
909
+ it('extracts the same host with and without userinfo', () => {
910
+ expect(tldts.getHostname('http://host.com/p')).to.equal('host.com');
911
+ expect(tldts.getHostname('http://user@host.com/p')).to.equal(
912
+ 'host.com',
913
+ );
914
+ expect(tldts.getHostname('http://user:pass@host.com/p')).to.equal(
915
+ 'host.com',
916
+ );
917
+ });
918
+
919
+ it('extracts the same host with and without a port', () => {
920
+ expect(tldts.getHostname('http://host.com:8080/p')).to.equal(
921
+ 'host.com',
922
+ );
923
+ });
924
+
925
+ it('extracts the same host with a trailing-dot (complex) authority', () => {
926
+ expect(tldts.getHostname('http://host.com./p')).to.equal('host.com');
927
+ });
928
+
929
+ it('extracts a bracketed IPv6 host (complex authority)', () => {
930
+ expect(tldts.getHostname('http://[::1]/p')).to.equal('::1');
931
+ });
932
+ });
933
+
934
+ describe('full parse() result parity at the boundary', () => {
935
+ // The fused verdict gates parseImpl BEFORE public-suffix/domain
936
+ // extraction, so a valid simple host must still produce a full result and
937
+ // an invalid one must zero every field.
938
+ it('produces a full result for a valid simple host', () => {
939
+ expect(tldts.parse('http://sub.example.com/p')).to.deep.include({
940
+ hostname: 'sub.example.com',
941
+ domain: 'example.com',
942
+ publicSuffix: 'com',
943
+ });
944
+ });
945
+
946
+ it('zeroes domain and publicSuffix for an invalid simple host', () => {
947
+ expect(tldts.parse('http://-example.com/p')).to.deep.include({
948
+ hostname: null,
949
+ domain: null,
950
+ publicSuffix: null,
951
+ });
952
+ });
953
+ });
954
+
955
+ describe('fusion-inactive controls (must match the validating path)', () => {
956
+ it('validateHostname:false keeps a host the fused path would reject', () => {
957
+ // Fusion never runs; the raw host is returned verbatim.
958
+ expect(
959
+ tldts.parse('http://-example.com/p', { validateHostname: false })
960
+ .hostname,
961
+ ).to.equal('-example.com');
962
+ expect(
963
+ tldts.parse('http://' + rep('a', 64) + '.com/p', {
964
+ validateHostname: false,
965
+ }).hostname,
966
+ ).to.equal(rep('a', 64) + '.com');
967
+ });
968
+
969
+ it('mixedInputs:false (URL-only path) matches the default for a valid host', () => {
970
+ const url = 'http://sub.example.com/p';
971
+ expect(tldts.parse(url, { mixedInputs: false })).to.deep.equal(
972
+ tldts.parse(url),
973
+ );
974
+ });
975
+
976
+ it('mixedInputs:false rejects an invalid simple host like the default', () => {
977
+ const url = 'http://-example.com/p';
978
+ expect(tldts.parse(url, { mixedInputs: false }).hostname).to.equal(
979
+ null,
980
+ );
981
+ });
982
+
983
+ it('a bare valid hostname (reference path) parses identically to its URL form', () => {
984
+ expect(tldts.parse('sub.example.com').hostname).to.equal(
985
+ 'sub.example.com',
986
+ );
987
+ expect(tldts.parse('sub.example.com').domain).to.equal('example.com');
988
+ });
989
+ });
990
+ });
991
+
734
992
  describe('getDomainWithoutSuffix method', () => {
735
993
  it('should return null if the domain cannot be found', () => {
736
994
  expect(tldts.getDomainWithoutSuffix('not-a-validHost')).to.equal(null);