initial release
[squeep-web-linking] / test / lib / rfc8288-web-linking.js
diff --git a/test/lib/rfc8288-web-linking.js b/test/lib/rfc8288-web-linking.js
new file mode 100644 (file)
index 0000000..6bdc188
--- /dev/null
@@ -0,0 +1,275 @@
+/* eslint-env mocha */
+'use strict';
+
+const assert = require('assert');
+
+describe('RFC 8288 Header parser', function () {
+  const { parse, SyntaxError: PEGSyntaxError } = require('../../lib/rfc8288-web-linking');
+
+  function checkParser(linkHeader, expectedResult) {
+    const result = parse(linkHeader.replace(/\r?\n|\r/gm, ''));
+    assert.deepStrictEqual(result, expectedResult);
+  }
+
+  describe('§3.3 Relation Type', function () {
+    it('Must ignore occurrences after the first', function () {
+      const linkHeader = [
+        '<http://example.org/>;',
+        'rel="start"; rel="ignore"; foo',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: 'http://example.org/',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'start',
+            },
+            {
+              extended: false,
+              name: 'foo',
+              value: null,
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+  });
+
+  describe('§3.5 Link Header Field Examples', function () {
+    it('a', function () {
+      const linkHeader = [
+        '<http://example.com/TheBook/chapter2>; rel="previous";',
+        'title="previous chapter"',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: 'http://example.com/TheBook/chapter2',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'previous',
+            },
+            {
+              extended: false,
+              name: 'title',
+              value: 'previous chapter',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('b', function () {
+      const linkHeader = '</>; rel="http://example.net/foo"';
+      const expectedResult = [
+        {
+          target: '/',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'http://example.net/foo',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('c', function () {
+      const linkHeader = '</terms>; rel="copyright"; anchor="#foo"';
+      const expectedResult = [
+        {
+          target: '/terms',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'copyright',
+            },
+            {
+              extended: false,
+              name: 'anchor',
+              value: '#foo',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('d', function () {
+      const linkHeader = [
+        '</TheBook/chapter2>;',
+        'rel="previous"; title*=UTF-8\'de\'letztes%20Kapitel,',
+        '</TheBook/chapter4>;',
+        'rel="next"; title*=UTF-8\'de\'n%c3%a4chstes%20Kapitel',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: '/TheBook/chapter2',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'previous',
+            },
+            {
+              extended: true,
+              name: 'title*',
+              value: 'UTF-8\'de\'letztes%20Kapitel',
+            },
+          ],
+        },
+        {
+          target: '/TheBook/chapter4',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'next',
+            },
+            {
+              extended: true,
+              name: 'title*',
+              value: 'UTF-8\'de\'n%c3%a4chstes%20Kapitel',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('e', function () {
+      const linkHeader = [
+        '<http://example.org/>;',
+        'rel="start http://example.net/relation/other"',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: 'http://example.org/',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'start http://example.net/relation/other',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('f', function () {
+      const linkHeader = [
+        '<https://example.org/>; rel="start",',
+        '    <https://example.org/index>; rel="index"',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: 'https://example.org/',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'start',
+            },
+          ],
+        },
+        {
+          target: 'https://example.org/index',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'index',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+  }); // §3.5 Link Header Field Examples
+
+  describe('Unusual Cases', function () {
+    it('handles uris with semicolons', function () {
+      const linkHeader = [
+        '<http://example.com/strange;url;indeed>; rel="self";',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: 'http://example.com/strange;url;indeed',
+          attributes: [
+            {
+              extended: false,
+              name: 'rel',
+              value: 'self',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+    it('handles value-less attributes', function () {
+      const linkHeader = [
+        '</>; special; rel="special";',
+      ].join('\n');
+      const expectedResult = [
+        {
+          target: '/',
+          attributes: [
+            {
+              extended: false,
+              name: 'special',
+              value: null,
+            },
+            {
+              extended: false,
+              name: 'rel',
+              value: 'special',
+            },
+          ],
+        },
+      ];
+      checkParser(linkHeader, expectedResult);
+    });
+  }); // Unusual Cases
+
+  describe('Extended Values', function () {
+    it('decodes extended value', function () {
+      const extendedValue = 'UTF-8\'\'%c2%a3%20and%20%e2%82%ac%20rates';
+      const expectedResult = {
+        encoding: 'UTF-8',
+        language: null,
+        value: '£ and € rates',
+      };
+      const result = parse(extendedValue, { startRule: 'extendedValue' });
+      assert.deepStrictEqual(result, expectedResult);
+    });
+  }); // Extended Values
+
+  describe('Failures', function () {
+    it('does not parse invalid header', function () {
+      const linkHeader = 'not a link header';
+      const expectedResult = [];
+      try {
+        checkParser(linkHeader, expectedResult);
+        assert.fail('unexpected success');
+      } catch (e) {
+        assert.strictEqual(e.name, 'SyntaxError');
+      }
+    });
+    it('does not parse partial invalid header', function () {
+      const linkHeader = '<https://example.com/foo>; rel="valid", not a link header';
+      const expectedResult = [];
+      try {
+        checkParser(linkHeader, expectedResult);
+        assert.fail('unexpected success');
+      } catch (e) {
+        assert(e instanceof PEGSyntaxError);
+        assert.strictEqual(e.name, 'SyntaxError');
+      }
+    });
+  }); // Failures
+
+});
\ No newline at end of file