module http fn test_header_new() { h := new_header(HeaderConfig{ key: .accept, value: 'nothing' }, key: .expires value: 'yesterday' ) assert h.contains(.accept) assert h.contains(.expires) accept := h.get(.accept) or { '' } expires := h.get(.expires) or { '' } assert accept == 'nothing' assert expires == 'yesterday' } fn test_header_invalid_key() { mut h := new_header() h.add_custom('space is invalid', ':(') or { return } panic('should have returned') } fn test_header_adds_multiple() { mut h := new_header() h.add(.accept, 'one') h.add(.accept, 'two') assert h.values(.accept) == ['one', 'two'] } fn test_header_get() ? { mut h := new_header(key: .dnt, value: 'one') h.add_custom('dnt', 'two') ? dnt := h.get_custom('dnt') or { '' } exact := h.get_custom('dnt', exact: true) or { '' } assert dnt == 'one' assert exact == 'two' } fn test_header_set() ? { mut h := new_header(HeaderConfig{ key: .dnt, value: 'one' }, key: .dnt value: 'two' ) assert h.values(.dnt) == ['one', 'two'] h.set_custom('DNT', 'three') ? assert h.values(.dnt) == ['three'] } fn test_header_delete() { mut h := new_header(HeaderConfig{ key: .dnt, value: 'one' }, key: .dnt value: 'two' ) assert h.values(.dnt) == ['one', 'two'] h.delete(.dnt) assert h.values(.dnt) == [] } fn test_header_delete_not_existing() { mut h := new_header() assert h.data.len == 0 assert h.keys.len == 0 h.delete(.dnt) assert h.data.len == 0 assert h.keys.len == 0 } fn test_custom_header() ? { mut h := new_header() h.add_custom('AbC', 'dEf') ? h.add_custom('aBc', 'GhI') ? assert h.custom_values('AbC', exact: true) == ['dEf'] assert h.custom_values('aBc', exact: true) == ['GhI'] assert h.custom_values('ABC') == ['dEf', 'GhI'] assert h.custom_values('abc') == ['dEf', 'GhI'] assert h.keys() == ['AbC', 'aBc'] h.delete_custom('AbC') h.delete_custom('aBc') h.add_custom('abc', 'def') ? assert h.custom_values('abc') == ['def'] assert h.custom_values('ABC') == ['def'] assert h.keys() == ['abc'] h.delete_custom('abc') h.add_custom('accEPT', '*/*') ? assert h.custom_values('ACCept') == ['*/*'] assert h.values(.accept) == ['*/*'] assert h.keys() == ['accEPT'] } fn test_contains_custom() ? { mut h := new_header() h.add_custom('Hello', 'world') ? assert h.contains_custom('hello') assert h.contains_custom('HELLO') assert h.contains_custom('Hello', exact: true) assert h.contains_custom('hello', exact: true) == false assert h.contains_custom('HELLO', exact: true) == false } fn test_get_custom() ? { mut h := new_header() h.add_custom('Hello', 'world') ? assert h.get_custom('hello') ? == 'world' assert h.get_custom('HELLO') ? == 'world' assert h.get_custom('Hello', exact: true) ? == 'world' if _ := h.get_custom('hello', exact: true) { // should be none assert false } if _ := h.get_custom('HELLO', exact: true) { // should be none assert false } } fn test_starting_with() ? { mut h := new_header() h.add_custom('Hello-1', 'world') ? h.add_custom('Hello-21', 'world') ? assert h.starting_with('Hello-') ? == 'Hello-1' assert h.starting_with('Hello-2') ? == 'Hello-21' } fn test_custom_values() ? { mut h := new_header() h.add_custom('Hello', 'world') ? assert h.custom_values('hello') == ['world'] assert h.custom_values('HELLO') == ['world'] assert h.custom_values('Hello', exact: true) == ['world'] assert h.custom_values('hello', exact: true) == [] assert h.custom_values('HELLO', exact: true) == [] } fn test_coerce() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add(.accept, 'bar') assert h.values(.accept) == ['foo', 'bar'] assert h.keys().len == 2 h.coerce() assert h.values(.accept) == ['foo', 'bar'] assert h.keys() == ['accept'] // takes the first occurrence } fn test_coerce_canonicalize() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add(.accept, 'bar') assert h.values(.accept) == ['foo', 'bar'] assert h.keys().len == 2 h.coerce(canonicalize: true) assert h.values(.accept) == ['foo', 'bar'] assert h.keys() == ['Accept'] // canonicalize header } fn test_coerce_custom() ? { mut h := new_header() h.add_custom('Hello', 'foo') ? h.add_custom('hello', 'bar') ? h.add_custom('HELLO', 'baz') ? assert h.custom_values('hello') == ['foo', 'bar', 'baz'] assert h.keys().len == 3 h.coerce() assert h.custom_values('hello') == ['foo', 'bar', 'baz'] assert h.keys() == ['Hello'] // takes the first occurrence } fn test_coerce_canonicalize_custom() ? { mut h := new_header() h.add_custom('foo-BAR', 'foo') ? h.add_custom('FOO-bar', 'bar') ? assert h.custom_values('foo-bar') == ['foo', 'bar'] assert h.keys().len == 2 h.coerce(canonicalize: true) assert h.custom_values('foo-bar') == ['foo', 'bar'] assert h.keys() == ['Foo-Bar'] // capitalizes the header } fn test_render_version() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add_custom('Accept', 'bar') ? h.add(.accept, 'baz') s1_0 := h.render(version: .v1_0) assert s1_0.contains('accept: foo\r\n') assert s1_0.contains('Accept: bar\r\n') assert s1_0.contains('Accept: baz\r\n') s1_1 := h.render(version: .v1_1) assert s1_1.contains('accept: foo\r\n') assert s1_1.contains('Accept: bar\r\n') assert s1_1.contains('Accept: baz\r\n') s2_0 := h.render(version: .v2_0) assert s2_0.contains('accept: foo\r\n') assert s2_0.contains('accept: bar\r\n') assert s2_0.contains('accept: baz\r\n') } fn test_render_coerce() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add_custom('Accept', 'bar') ? h.add(.accept, 'baz') h.add(.host, 'host') s1_0 := h.render(version: .v1_1, coerce: true) assert s1_0.contains('accept: foo\r\n') assert s1_0.contains('accept: bar\r\n') assert s1_0.contains('accept: baz\r\n') assert s1_0.contains('Host: host\r\n') s1_1 := h.render(version: .v1_1, coerce: true) assert s1_1.contains('accept: foo\r\n') assert s1_1.contains('accept: bar\r\n') assert s1_1.contains('accept: baz\r\n') assert s1_1.contains('Host: host\r\n') s2_0 := h.render(version: .v2_0, coerce: true) assert s2_0.contains('accept: foo\r\n') assert s2_0.contains('accept: bar\r\n') assert s2_0.contains('accept: baz\r\n') assert s2_0.contains('host: host\r\n') } fn test_render_canonicalize() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add_custom('Accept', 'bar') ? h.add(.accept, 'baz') h.add(.host, 'host') s1_0 := h.render(version: .v1_1, canonicalize: true) assert s1_0.contains('Accept: foo\r\n') assert s1_0.contains('Accept: bar\r\n') assert s1_0.contains('Accept: baz\r\n') assert s1_0.contains('Host: host\r\n') s1_1 := h.render(version: .v1_1, canonicalize: true) assert s1_1.contains('Accept: foo\r\n') assert s1_1.contains('Accept: bar\r\n') assert s1_1.contains('Accept: baz\r\n') assert s1_1.contains('Host: host\r\n') s2_0 := h.render(version: .v2_0, canonicalize: true) assert s2_0.contains('accept: foo\r\n') assert s2_0.contains('accept: bar\r\n') assert s2_0.contains('accept: baz\r\n') assert s2_0.contains('host: host\r\n') } fn test_render_coerce_canonicalize() ? { mut h := new_header() h.add_custom('accept', 'foo') ? h.add_custom('Accept', 'bar') ? h.add(.accept, 'baz') h.add(.host, 'host') s1_0 := h.render(version: .v1_1, coerce: true, canonicalize: true) assert s1_0.contains('Accept: foo\r\n') assert s1_0.contains('Accept: bar\r\n') assert s1_0.contains('Accept: baz\r\n') assert s1_0.contains('Host: host\r\n') s1_1 := h.render(version: .v1_1, coerce: true, canonicalize: true) assert s1_1.contains('Accept: foo\r\n') assert s1_1.contains('Accept: bar\r\n') assert s1_1.contains('Accept: baz\r\n') assert s1_1.contains('Host: host\r\n') s2_0 := h.render(version: .v2_0, coerce: true, canonicalize: true) assert s2_0.contains('accept: foo\r\n') assert s2_0.contains('accept: bar\r\n') assert s2_0.contains('accept: baz\r\n') assert s2_0.contains('host: host\r\n') } fn test_str() ? { mut h := new_header() h.add(.accept, 'text/html') h.add_custom('Accept', 'image/jpeg') ? h.add_custom('X-custom', 'Hello') ? // key order is not guaranteed assert h.str() == 'Accept: text/html\r\nAccept: image/jpeg\r\nX-custom: Hello\r\n' || h.str() == 'X-custom: Hello\r\nAccept:text/html\r\nAccept: image/jpeg\r\n' } fn test_header_from_map() ? { h := new_header_from_map({ CommonHeader.accept: 'nothing' CommonHeader.expires: 'yesterday' }) assert h.contains(.accept) assert h.contains(.expires) assert h.get(.accept) or { '' } == 'nothing' assert h.get(.expires) or { '' } == 'yesterday' } fn test_custom_header_from_map() ? { h := new_custom_header_from_map({ 'Server': 'VWeb' 'foo': 'bar' }) ? assert h.contains_custom('server') assert h.contains_custom('foo') assert h.get_custom('server') or { '' } == 'VWeb' assert h.get_custom('foo') or { '' } == 'bar' } fn test_header_join() ? { h1 := new_header_from_map({ CommonHeader.accept: 'nothing' CommonHeader.expires: 'yesterday' }) h2 := new_custom_header_from_map({ 'Server': 'VWeb' 'foo': 'bar' }) ? h3 := h1.join(h2) // h1 is unchanged assert h1.contains(.accept) assert h1.contains(.expires) assert !h1.contains_custom('Server') assert !h1.contains_custom('foo') // h2 is unchanged assert !h2.contains(.accept) assert !h2.contains(.expires) assert h2.contains_custom('Server') assert h2.contains_custom('foo') // h3 has all four headers assert h3.contains(.accept) assert h3.contains(.expires) assert h3.contains_custom('Server') assert h3.contains_custom('foo') } fn parse_headers_test(s string, expected map[string]string) ? { assert parse_headers(s) ? == new_custom_header_from_map(expected) ? } fn test_parse_headers() ? { parse_headers_test('foo: bar', { 'foo': 'bar' }) ? parse_headers_test('foo: \t bar', { 'foo': 'bar' }) ? parse_headers_test('foo: bar\r\n\tbaz', { 'foo': 'bar baz' }) ? parse_headers_test('foo: bar \r\n\tbaz\r\n buzz', { 'foo': 'bar baz buzz' }) ? parse_headers_test('foo: bar\r\nbar:baz', { 'foo': 'bar' 'bar': 'baz' }) ? parse_headers_test('foo: bar\r\nbar:baz\r\n', { 'foo': 'bar' 'bar': 'baz' }) ? parse_headers_test('foo: bar\r\nbar:baz\r\n\r\n', { 'foo': 'bar' 'bar': 'baz' }) ? assert parse_headers('foo: bar\r\nfoo:baz') ?.custom_values('foo') == ['bar', 'baz'] if x := parse_headers(' oops: oh no') { return error('should have errored, but got $x') } } fn test_set_cookie() { // multiple Set-Cookie headers should be sent when rendered mut h := new_header() h.add(.set_cookie, 'foo') h.add(.set_cookie, 'bar') assert h.render() == 'Set-Cookie: foo\r\nSet-Cookie: bar\r\n' }