intranet-core 2.0.0 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cb2eacb573a839642af1a0919d07ed5c43b66ebd
4
- data.tar.gz: ef44c9521483faa96b12b2b518433b2066bb816a
2
+ SHA256:
3
+ metadata.gz: a6860141eca0145a6d51787a137641ead4978b69d2f7b2d28ce5dcf1a5193f42
4
+ data.tar.gz: 6f43415cdf7334c81466b1205a838b92762c15dff3fb8ca3e096ccb71ebecfa2
5
5
  SHA512:
6
- metadata.gz: 0b6c6c410f1715508866b01ad75336a2e1e18a41c5c4ba6b42a8aa7fc0786a7e0609ff6da04649a9aa9cac3f0bad3f8236ce2a2f3ebb8d67a8242acf269fc087
7
- data.tar.gz: 45ed729192de045ca4860f1197e0569c2e807011841539ab222855942e827a107e5dc9ddf15ffdb02f1b0a25249264d6a81513d8b79bd9fe3ed692d106bfba82
6
+ metadata.gz: 84eeb50385f879656140e5b385a98a50c3149fa7b6cb8d31fd49b97ce73d5f69eda2e3e33789876196c0eaccaa47721b089e7870a0b90880392f03aedb279600
7
+ data.tar.gz: 07e5122a4279dc99efe881651637e932ec36601b3007529e545b13b7ccaef2297ca05b7f6184f02a6693d61ad1147b162961c508992ce57ce09ce56a0a8de32e
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'core_extensions/hash'
3
4
  require_relative 'core_extensions/string'
4
5
  require_relative 'core_extensions/tree'
5
6
  require_relative 'core_extensions/webrick/httpresponse'
6
7
 
8
+ Hash.include CoreExtensions::Hash
7
9
  String.include CoreExtensions::String
8
10
  WEBrick::HTTPResponse.include CoreExtensions::WEBrick::HTTPResponse
9
11
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoreExtensions
4
+ # Extension of Ruby's standard library +Hash+ class.
5
+ module Hash
6
+ # Returns a copy of a hash with +keys+ excluded. Original hash is not
7
+ # modified.
8
+ # @return [Hash]
9
+ def except(*keys)
10
+ h = dup
11
+ keys.each { |key| h.delete(key) }
12
+ h
13
+ end
14
+ end
15
+ end
@@ -3,7 +3,6 @@
3
3
  require 'titleize'
4
4
 
5
5
  module CoreExtensions
6
- # @!visibility protected
7
6
  # Extension of Ruby's standard library +String+ class.
8
7
  module String
9
8
  # Replaces all accented characters in a string with their non-accented version.
@@ -22,6 +22,15 @@ module Intranet
22
22
  true
23
23
  end
24
24
 
25
+ # Specifies the absolute path to the resources directory for that module. This directory should
26
+ # contain three subdirectories: +haml/+, +locales/+ and +www/+.
27
+ # This method should be redefined to overwrite this default value with the actual resources
28
+ # directory path.
29
+ # @return [String] The absolute path to the resources directory for the module.
30
+ def resources_dir
31
+ File.join(__dir__, 'resources')
32
+ end
33
+
25
34
  # Destroys the responder instance.
26
35
  # This method gets called when server is shut down.
27
36
  def finalize
@@ -52,7 +52,7 @@ module Intranet
52
52
  # mounted under +<path>/design+ on the web server.
53
53
  # @raise [ArgumentError] If the +path+ is not valid.
54
54
  # @raise [Errno::EALREADY] If the server is already running.
55
- def register_module(responder, path, resources_dir)
55
+ def register_module(responder, path, resources_dir = responder.resources_dir)
56
56
  raise Errno::EALREADY if @server.status != :Stop
57
57
 
58
58
  @builder.register(responder, path)
@@ -80,6 +80,8 @@ module Intranet
80
80
  def stop
81
81
  @logger.info('Intranet::Runner: requesting system shutdown...')
82
82
  @server.shutdown
83
+ while @server.status != :Stop
84
+ end
83
85
  @builder.finalize
84
86
  @logger.close
85
87
  end
@@ -57,12 +57,12 @@ module Intranet
57
57
 
58
58
  # Generate header and footer when partial content is returned by the responder
59
59
  if status == 206 && mime_type == 'text/html'
60
- body = to_markup('skeleton', body: body, css: responder.css_dependencies,
61
- js: responder.js_dependencies, current_path: parsed_path)
60
+ body = add_header_and_footer(body, responder, parsed_path)
62
61
  status = 200
63
62
  end
64
63
  [status, mime_type, body]
65
- rescue KeyError, NoMethodError
64
+ rescue KeyError, NoMethodError => e
65
+ @logger.debug(e)
66
66
  [404, '', '']
67
67
  end
68
68
 
@@ -111,6 +111,16 @@ module Intranet
111
111
  [current_treenode.value, '/' + parsed_path.join('/'), '/' + unparsed_path.join('/')]
112
112
  end
113
113
 
114
+ # Encapsulate the given body in the default page skeleton, effectively
115
+ # adding page header and footer.
116
+ # @param body [String] The body of the page
117
+ # @param responder [Intranet::AbstractResponder] The responder that produced the body
118
+ # @param path [String] The path to the responder
119
+ def add_header_and_footer(body, responder, path)
120
+ to_markup('skeleton', body: body, css: responder.css_dependencies,
121
+ js: responder.js_dependencies, current_path: path)
122
+ end
123
+
114
124
  # Check whether a path is valid.
115
125
  # @param path [Array] The path to test.
116
126
  # @return [Boolean] True if the path is valid, False otherwise.
@@ -26,12 +26,12 @@ module Intranet
26
26
  # We could use request.request_uri (which is a URI object) but its path is not normalized
27
27
  # (it may contain '../' or event start with '..'). Hence we use request.path which has been
28
28
  # normalized with HTTPUtils::normalize_path, and request.query for the URL parameters.
29
- path = request.path
29
+ path = request.path.force_encoding('UTF-8')
30
30
  path += 'index.html' if path.end_with?('/')
31
31
 
32
32
  handle_redirections(request, path, response, '/index.html' => @builder.home_url)
33
33
 
34
- status, content_type, body = @builder.do_get(path, request.query)
34
+ status, content_type, body = @builder.do_get(path, encode_query(request.query))
35
35
 
36
36
  raise WEBrick::HTTPStatus[status] if WEBrick::HTTPStatus.error?(status)
37
37
 
@@ -48,9 +48,16 @@ module Intranet
48
48
  target = redirects.fetch(path)
49
49
  location = URI.join(request.request_uri, target).to_s
50
50
  response.set_redirect(WEBrick::HTTPStatus[307], location) if path != target
51
- rescue KeyError # rubocop:disable Lint/HandleExceptions
51
+ rescue KeyError
52
52
  # nothing to do
53
53
  end
54
+
55
+ # Reencodes the +query+ in UTF-8 strings for compatibility.
56
+ def encode_query(query)
57
+ query.map do |k, v|
58
+ { k.dup.force_encoding('UTF-8') => v.force_encoding('UTF-8') }
59
+ end.reduce({}, :merge)
60
+ end
54
61
  end
55
62
  end
56
63
  end
@@ -6,7 +6,7 @@ module Intranet
6
6
  NAME = 'intranet-core'
7
7
 
8
8
  # The version of the gem, according to semantic versionning.
9
- VERSION = '2.0.0'
9
+ VERSION = '2.2.0'
10
10
 
11
11
  # The URL of the gem homepage.
12
12
  HOMEPAGE_URL = 'https://rubygems.org/gems/intranet-core'
@@ -10,7 +10,8 @@ function openNavMenu() {
10
10
  }
11
11
 
12
12
  function closeNavMenu() {
13
- document.querySelectorAll('header nav')[0].style.width = '0';
13
+ /* Remove value property set in openNavMenu() */
14
+ document.querySelectorAll('header nav')[0].style.width = '';
14
15
  }
15
16
 
16
17
  function openModal() {
@@ -45,7 +45,6 @@ header {
45
45
  top: 0px;
46
46
  width: 100%;
47
47
  margin: auto;
48
- height: 57px;
49
48
  background-color: #1e262b;
50
49
  color: white;
51
50
  z-index: 2;
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'core_extensions/hash'
4
+
5
+ RSpec.describe CoreExtensions::Hash do
6
+ before do
7
+ Hash.include described_class
8
+ end
9
+
10
+ describe '#except' do
11
+ it 'should return a copy of the original hash with the given keys removed' do
12
+ tested_hash = { a: true, b: false, c: nil }
13
+ expect(tested_hash.except(:c)).to eql({ a: true, b: false })
14
+ expect(tested_hash).to eql({ a: true, b: false, c: nil })
15
+
16
+ tested_hash = { a: 100, b: 200, c: 300 }
17
+ expect(tested_hash.except(:a)).to eql({ b: 200, c: 300 })
18
+ expect(tested_hash.except(:a, :c)).to eql({ b: 200 })
19
+ expect(tested_hash).to eql({ a: 100, b: 200, c: 300 })
20
+ end
21
+ end
22
+ end
@@ -15,71 +15,67 @@ RSpec.describe Intranet::Core do
15
15
 
16
16
  describe '#start' do
17
17
  it 'should start an HTTP server on an available port' do
18
- begin
19
- # make sure port 80 & 8080 are not available
20
- @intranet8080 = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
21
- thread = Thread.new do
22
- @intranet8080.start
23
- end
24
- # Wait for the server on port 8080 to be running
25
- while @intranet8080.instance_variable_get(:@server).status != :Running
26
- end
18
+ # make sure port 80 & 8080 are not available
19
+ @intranet8080 = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
20
+ thread8080 = Thread.new { @intranet8080.start }
21
+ while @intranet8080.instance_variable_get(:@server).status != :Running
22
+ end
23
+ expect(@intranet8080.port).to be >= 8080
27
24
 
28
- intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
29
- expect(intranet.port).to eql(
30
- intranet.instance_variable_get(:@server).instance_variable_get(:@config)[:Port]
31
- )
32
- expect(intranet.port).not_to eql(8080)
33
- ensure
34
- intranet.stop if intranet.respond_to?(:stop)
35
- Thread.kill(thread)
36
- thread.join
25
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
26
+ thread = Thread.new { @intranet.start }
27
+ while @intranet.instance_variable_get(:@server).status != :Running
37
28
  end
29
+ expect(@intranet.port).to be > 8080
30
+
31
+ @intranet.stop
32
+ thread&.join
33
+ @intranet8080.stop
34
+ thread8080&.join
38
35
  end
39
36
 
40
37
  it 'should start searching for an available port at the given +preferred_port+' do
41
- intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL), 9090)
42
- expect(intranet.port).to be >= 9090
38
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL), 9090)
39
+ thread = Thread.new { @intranet.start }
40
+ while @intranet.instance_variable_get(:@server).status != :Running
41
+ end
42
+
43
+ expect(@intranet.port).to be >= 9090
44
+
45
+ @intranet.stop
46
+ thread&.join
43
47
  end
44
48
 
45
49
  it 'should serve the /design directory' do
46
- begin
50
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
51
+ thread = Thread.new { @intranet.start }
52
+ while @intranet.instance_variable_get(:@server).status != :Running
53
+ end
54
+
55
+ socket = TCPSocket.new('localhost', @intranet.port)
56
+ socket.puts("GET /design/favicon.ico HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
57
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
58
+ socket.close
59
+
60
+ @intranet.stop
61
+ thread&.join
62
+ end
63
+
64
+ context 'when no module is registered' do
65
+ it 'should return HTTP error 404 when requested for /index.html' do
47
66
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
48
- thread = Thread.new do
49
- @intranet.start
50
- end
67
+ thread = Thread.new { @intranet.start }
51
68
  while @intranet.instance_variable_get(:@server).status != :Running
52
69
  end
53
70
 
54
71
  socket = TCPSocket.new('localhost', @intranet.port)
55
- socket.puts("GET /design/favicon.ico HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
56
- expect(socket.gets).to include('HTTP/1.1 200 OK')
57
- ensure
72
+ socket.puts("GET /index.html HTTP/1.1\r\n" \
73
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
74
+ expect(socket.gets).to include('HTTP/1.1 404 Not Found')
58
75
  socket.close
59
- Thread.kill(thread)
60
- thread.join
61
- end
62
- end
63
76
 
64
- context 'when no module is registered' do
65
- it 'should return HTTP error 404 when requested for /index.html' do
66
- begin
67
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
68
- thread = Thread.new do
69
- @intranet.start
70
- end
71
- while @intranet.instance_variable_get(:@server).status != :Running
72
- end
73
-
74
- socket = TCPSocket.new('localhost', @intranet.port)
75
- socket.puts("GET /index.html HTTP/1.1\r\n" \
76
- "Host: localhost:#{@intranet.port}\r\n\r\n")
77
- expect(socket.gets).to include('HTTP/1.1 404 Not Found')
78
- ensure
79
- socket.close
80
- Thread.kill(thread)
81
- thread.join
82
- end
77
+ @intranet.stop
78
+ thread&.join
83
79
  end
84
80
  end
85
81
  end
@@ -87,58 +83,71 @@ RSpec.describe Intranet::Core do
87
83
  describe '#register_module' do
88
84
  context 'registering a module when the server is running' do
89
85
  it 'should fail' do
90
- begin
91
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
92
- r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
93
- thread = Thread.new do
94
- @intranet.start
95
- end
96
- while @intranet.instance_variable_get(:@server).status != :Running
97
- end
98
-
99
- expect { @intranet.register_module(r, ['path'], '') }.to raise_error Errno::EALREADY
100
- ensure
101
- Thread.kill(thread)
102
- thread.join
86
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
87
+ thread = Thread.new { @intranet.start }
88
+ while @intranet.instance_variable_get(:@server).status != :Running
103
89
  end
90
+
91
+ r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
92
+ expect { @intranet.register_module(r, ['path'], '') }.to raise_error Errno::EALREADY
93
+
94
+ @intranet.stop
95
+ thread&.join
104
96
  end
105
97
  end
106
98
 
107
99
  context 'registering a module with an invalid path' do
108
100
  it 'should fail' do
109
101
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
102
+
110
103
  r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
111
104
  expect { @intranet.register_module(r, [], '') }.to raise_error ArgumentError
112
105
  expect { @intranet.register_module(r, %w[1 2 3], '') }.to raise_error ArgumentError
113
106
  expect { @intranet.register_module(r, ['', 'valid'], '') }.to raise_error ArgumentError
114
107
  expect { @intranet.register_module(r, ['Invalid'], '') }.to raise_error ArgumentError
115
108
  expect { @intranet.register_module(r, 'fo', '') }.to raise_error ArgumentError
109
+
110
+ thread = Thread.new { @intranet.start }
111
+ while @intranet.instance_variable_get(:@server).status != :Running
112
+ end
113
+
114
+ @intranet.stop
115
+ thread&.join
116
116
  end
117
117
  end
118
118
 
119
119
  context 'registering an invalid module' do
120
120
  it 'should fail' do
121
121
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
122
+
122
123
  expect { @intranet.register_module(nil, ['path'], '') }.to raise_error ArgumentError
124
+
125
+ thread = Thread.new { @intranet.start }
126
+ while @intranet.instance_variable_get(:@server).status != :Running
127
+ end
128
+
129
+ @intranet.stop
130
+ thread&.join
123
131
  end
124
132
  end
125
133
 
126
134
  context 'when a valid module is registered' do
127
135
  before(:each) do
128
136
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
137
+
129
138
  responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
139
+ # Third argument of register_module() is optional, so we test both cases.
130
140
  @intranet.register_module(responder, %w[responder], responder.resources_dir)
131
- @intranet.register_module(responder, %w[resp onder], responder.resources_dir)
132
- @thread = Thread.new do
133
- @intranet.start
134
- end
141
+ @intranet.register_module(responder, %w[resp onder])
142
+
143
+ @thread = Thread.new { @intranet.start }
135
144
  while @intranet.instance_variable_get(:@server).status != :Running
136
145
  end
137
146
  end
138
147
 
139
148
  after(:each) do
140
- Thread.kill(@thread)
141
- @thread.join
149
+ @intranet.stop
150
+ @thread&.join
142
151
  end
143
152
 
144
153
  it 'should be used to serve URI relative to the module root' do
@@ -177,157 +186,143 @@ RSpec.describe Intranet::Core do
177
186
  end
178
187
 
179
188
  context 'given a valid and registered module' do
180
- it 'should be called with the URL path and query' do
181
- begin
182
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
183
- responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
184
- @intranet.register_module(responder, ['responder'], responder.resources_dir)
185
- thread = Thread.new do
186
- @intranet.start
187
- end
188
- while @intranet.instance_variable_get(:@server).status != :Running
189
- end
190
-
191
- socket = TCPSocket.new('localhost', @intranet.port)
192
- socket.puts("GET /responder/query?var1=value1&var2=value2 HTTP/1.1\r\n" \
193
- "Host: localhost:#{@intranet.port}\r\n\r\n")
194
- expect(socket.gets).to include('HTTP/1.1 200 OK')
195
- while (line = socket.gets.chomp) # consume HTTP response headers
196
- break if line.empty?
197
- end
198
- line = socket.gets.chomp
199
- expect(line).to eql({ 'var1' => 'value1', 'var2' => 'value2' }.to_s)
200
- socket.close
201
-
202
- socket = TCPSocket.new('localhost', @intranet.port)
203
- socket.puts("GET /responder/query?foo=bar&baz=boz HTTP/1.1\r\n" \
204
- "Host: localhost:#{@intranet.port}\r\n\r\n")
205
- expect(socket.gets).to include('HTTP/1.1 200 OK')
206
- while (line = socket.gets.chomp) # consume HTTP response headers
207
- break if line.empty?
208
- end
209
- line = socket.gets.chomp
210
- expect(line).to eql({ 'foo' => 'bar', 'baz' => 'boz' }.to_s)
211
- ensure
212
- socket.close
213
- Thread.kill(thread)
214
- thread.join
189
+ it 'should be called with the decoded URL path and query in UTF-8 encoding' do
190
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
191
+
192
+ responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
193
+ @intranet.register_module(responder, ['responder'], responder.resources_dir)
194
+
195
+ thread = Thread.new { @intranet.start }
196
+ while @intranet.instance_variable_get(:@server).status != :Running
197
+ end
198
+
199
+ socket = TCPSocket.new('localhost', @intranet.port)
200
+ socket.puts("GET /responder/query%20t?var1=value1&var2=value2 HTTP/1.1\r\n" \
201
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
202
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
203
+ while (line = socket.gets.chomp) # consume HTTP response headers
204
+ break if line.empty?
215
205
  end
206
+ line = socket.gets.chomp
207
+ expect(line).to eql(
208
+ 'PATH=/query t (UTF-8), ' \
209
+ 'QUERY={var1 (UTF-8) => value1 (UTF-8),var2 (UTF-8) => value2 (UTF-8)}'
210
+ )
211
+ socket.close
212
+
213
+ @intranet.stop
214
+ thread&.join
216
215
  end
217
216
  end
218
217
 
219
218
  context 'given a module returning partial HTML content' do
220
219
  it 'should be called to retrieve the body of the page' do
221
- begin
222
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
223
- responder = Intranet::TestResponder.new(
224
- {
225
- '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
226
- },
227
- ['/responder.css', 'nav.css'],
228
- ['module.js', '/js/interactive.js']
229
- )
230
- @intranet.register_module(responder, ['r'], responder.resources_dir)
231
- thread = Thread.new do
232
- @intranet.start
233
- end
234
- while @intranet.instance_variable_get(:@server).status != :Running
235
- end
236
-
237
- socket = TCPSocket.new('localhost', @intranet.port)
238
- socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
239
-
240
- # Return code: HTTP error 200
241
- expect(socket.gets).to include('HTTP/1.1 200 OK')
242
-
243
- while (line = socket.gets.chomp) # consume HTTP response headers
244
- break if line.empty?
245
- end
246
- html = socket.readpartial(4096) # read rest of data
247
-
248
- # Returned HTML document: includes the partial content and the title
249
- expect(html).to match(%r{<main>.*PARTIAL_CONTENT.*</main>}m)
250
- expect(html).to match(%r{<head>.*<title>.*MyTitle.*</title>.*</head>}m)
251
-
252
- # Returned HTML document: includes the hostname in title, h1-title and footer
253
- hostname = Socket.gethostname
254
- expect(html).to match(%r{<head>.*<title>.*#{hostname.capitalize}.*</title>.*</head>}m)
255
- expect(html).to match(%r{<body>.*<h1>.*#{hostname.capitalize}.*</h1>.*</body>}m)
256
- expect(html).to match(%r{<footer>.*#{hostname}.*</footer>}m)
257
-
258
- # Returned HTML document: includes all CSS dependencies, relative or absolute path
259
- expect(html).to match(%r{<link href='/responder.css' rel='stylesheet' type='text/css'})
260
- expect(html).to match(%r{<link href='/r/nav.css' rel='stylesheet' type='text/css'})
261
-
262
- # Returned HTML document: includes all JS dependencies
263
- expect(html).to match(%r{<script defer='defer' src='/r/module.js'></script>})
264
- expect(html).to match(%r{<script defer='defer' src='/js/interactive.js'></script>})
265
-
266
- # Returned HTML document: includes Intranet Core name, version and URL
267
- expect(html).to match(
268
- %r{<footer>.*<a href='#{Intranet::Core::HOMEPAGE_URL}'.*>#{Intranet::Core::NAME}</a>.*#{Intranet::Core::VERSION}.*</footer>}m # rubocop:disable Metrics/LineLength
269
- )
270
-
271
- # Returned HTML document: includes all registered modules version name, version and URL
272
- expect(html).to match(
273
- %r{<footer>.*<a href='http://nil/'.*>test-responder</a>.*0.0.0.*</footer>}m
274
- )
275
- ensure
276
- socket.close
277
- Thread.kill(thread)
278
- thread.join
220
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
221
+
222
+ responder = Intranet::TestResponder.new(
223
+ {
224
+ '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
225
+ },
226
+ ['/responder.css', 'nav.css'],
227
+ ['module.js', '/js/interactive.js']
228
+ )
229
+ @intranet.register_module(responder, ['r'], responder.resources_dir)
230
+
231
+ thread = Thread.new { @intranet.start }
232
+ while @intranet.instance_variable_get(:@server).status != :Running
279
233
  end
234
+
235
+ socket = TCPSocket.new('localhost', @intranet.port)
236
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
237
+
238
+ # Return code: HTTP error 200
239
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
240
+
241
+ while (line = socket.gets.chomp) # consume HTTP response headers
242
+ break if line.empty?
243
+ end
244
+ html = socket.readpartial(4096) # read rest of data
245
+ socket.close
246
+
247
+ # Returned HTML document: includes the partial content and the title
248
+ expect(html).to match(%r{<main>.*PARTIAL_CONTENT.*</main>}m)
249
+ expect(html).to match(%r{<head>.*<title>.*MyTitle.*</title>.*</head>}m)
250
+
251
+ # Returned HTML document: includes the hostname in title, h1-title and footer
252
+ hostname = Socket.gethostname
253
+ expect(html).to match(%r{<head>.*<title>.*#{hostname.capitalize}.*</title>.*</head>}m)
254
+ expect(html).to match(%r{<body>.*<h1>.*#{hostname.capitalize}.*</h1>.*</body>}m)
255
+ expect(html).to match(%r{<footer>.*#{hostname}.*</footer>}m)
256
+
257
+ # Returned HTML document: includes all CSS dependencies, relative or absolute path
258
+ expect(html).to match(%r{<link href='/responder.css' rel='stylesheet' type='text/css'})
259
+ expect(html).to match(%r{<link href='/r/nav.css' rel='stylesheet' type='text/css'})
260
+
261
+ # Returned HTML document: includes all JS dependencies
262
+ expect(html).to match(%r{<script defer='defer' src='/r/module.js'></script>})
263
+ expect(html).to match(%r{<script defer='defer' src='/js/interactive.js'></script>})
264
+
265
+ # Returned HTML document: includes Intranet Core name, version and URL
266
+ expect(html).to match(
267
+ %r{<footer>.*<a href='#{Intranet::Core::HOMEPAGE_URL}'.*>#{Intranet::Core::NAME}</a>.*#{Intranet::Core::VERSION}.*</footer>}m # rubocop:disable Layout/LineLength
268
+ )
269
+
270
+ # Returned HTML document: includes all registered modules version name, version and URL
271
+ expect(html).to match(
272
+ %r{<footer>.*<a href='http://nil/'.*>test-responder</a>.*0.0.0.*</footer>}m
273
+ )
274
+
275
+ @intranet.stop
276
+ thread&.join
280
277
  end
281
278
  it 'should be called to update the main navigation menu' do
282
- begin
283
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
284
- responder = Intranet::TestResponder.new(
285
- '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
286
- )
287
- other_responder = Intranet::TestResponder.new({}, [], [], true)
288
- @intranet.register_module(responder, %w[r], responder.resources_dir)
289
- @intranet.register_module(responder, %w[dep_th1], responder.resources_dir)
290
- @intranet.register_module(responder, %w[depth2 res_p1], responder.resources_dir)
291
- @intranet.register_module(responder, %w[depth2 resp2], responder.resources_dir)
292
- @intranet.register_module(other_responder, %w[depth2 resp], other_responder.resources_dir)
293
- @intranet.register_module(other_responder, %w[other1], other_responder.resources_dir)
294
- @intranet.register_module(other_responder, %w[other2 res1], other_responder.resources_dir)
295
- @intranet.register_module(other_responder, %w[other2 res2], other_responder.resources_dir)
296
- thread = Thread.new do
297
- @intranet.start
298
- end
299
- while @intranet.instance_variable_get(:@server).status != :Running
300
- end
301
-
302
- socket = TCPSocket.new('localhost', @intranet.port)
303
- socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
304
-
305
- # Return code: HTTP error 200
306
- expect(socket.gets).to include('HTTP/1.1 200 OK')
307
-
308
- while (line = socket.gets.chomp)
309
- break if line.empty?
310
- end
311
- html = socket.readpartial(4096) # read rest of data
312
-
313
- # Returned HTML document main menu
314
- expect(html).to match(%r{<a href='/dep_th1/index.html'>.*Dep Th1.*</a>})
315
- expect(html).not_to match(%r{<a href='/other1/index.html'>})
316
- expect(html).to match(
317
- %r{<a>.*Depth2.*</a>.*<ul>.*<a href='/depth2/res_p1/index.html'>.*Res P1.*</a>.*</ul>}m
318
- )
319
- expect(html).to match(
320
- %r{<a>.*Depth2.*</a>.*<ul>.*<a href='/depth2/resp2/index.html'>.*Resp2.*</a>.*</ul>}m
321
- )
322
- expect(html).not_to match(%r{<a href='/depth2/resp/index.html'>})
323
- expect(html).not_to match(%r{<a>.*Other2.*</a>}m)
324
- expect(html).not_to match(%r{<a href='/other2/res1/index.html'>})
325
- expect(html).not_to match(%r{<a href='/other2/res2/index.html'>})
326
- ensure
327
- socket.close
328
- Thread.kill(thread)
329
- thread.join
279
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
280
+
281
+ responder = Intranet::TestResponder.new(
282
+ '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
283
+ )
284
+ other_responder = Intranet::TestResponder.new({}, [], [], true)
285
+ @intranet.register_module(responder, %w[r], responder.resources_dir)
286
+ @intranet.register_module(responder, %w[dep_th1], responder.resources_dir)
287
+ @intranet.register_module(responder, %w[depth2 res_p1], responder.resources_dir)
288
+ @intranet.register_module(responder, %w[depth2 resp2], responder.resources_dir)
289
+ @intranet.register_module(other_responder, %w[depth2 resp], other_responder.resources_dir)
290
+ @intranet.register_module(other_responder, %w[other1], other_responder.resources_dir)
291
+ @intranet.register_module(other_responder, %w[other2 res1], other_responder.resources_dir)
292
+ @intranet.register_module(other_responder, %w[other2 res2], other_responder.resources_dir)
293
+
294
+ thread = Thread.new { @intranet.start }
295
+ while @intranet.instance_variable_get(:@server).status != :Running
330
296
  end
297
+
298
+ socket = TCPSocket.new('localhost', @intranet.port)
299
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
300
+
301
+ # Return code: HTTP error 200
302
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
303
+
304
+ while (line = socket.gets.chomp)
305
+ break if line.empty?
306
+ end
307
+ html = socket.readpartial(4096) # read rest of data
308
+ socket.close
309
+
310
+ # Returned HTML document main menu
311
+ expect(html).to match(%r{<a href='/dep_th1/index.html'>.*Dep Th1.*</a>})
312
+ expect(html).not_to match(%r{<a href='/other1/index.html'>})
313
+ expect(html).to match(
314
+ %r{<a>.*Depth2.*</a>.*<ul>.*<a href='/depth2/res_p1/index.html'>.*Res P1.*</a>.*</ul>}m
315
+ )
316
+ expect(html).to match(
317
+ %r{<a>.*Depth2.*</a>.*<ul>.*<a href='/depth2/resp2/index.html'>.*Resp2.*</a>.*</ul>}m
318
+ )
319
+ expect(html).not_to match(%r{<a href='/depth2/resp/index.html'>})
320
+ expect(html).not_to match(%r{<a>.*Other2.*</a>}m)
321
+ expect(html).not_to match(%r{<a href='/other2/res1/index.html'>})
322
+ expect(html).not_to match(%r{<a href='/other2/res2/index.html'>})
323
+
324
+ @intranet.stop
325
+ thread&.join
331
326
  end
332
327
  end
333
328
  end
@@ -336,66 +331,61 @@ RSpec.describe Intranet::Core do
336
331
  context 'given a relative URL' do
337
332
  it 'should fail' do
338
333
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
334
+
339
335
  expect { @intranet.home_url = 'foo/index.html' }.to raise_error ArgumentError
340
- end
341
- end
342
336
 
343
- context 'given an absolute URL' do
344
- it 'should set up a redirection from /index.html to the provided URL' do
345
- begin
346
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
347
- @intranet.home_url = '/responder/index.html'
348
- thread = Thread.new do
349
- @intranet.start
350
- end
351
- while @intranet.instance_variable_get(:@server).status != :Running
352
- end
353
-
354
- socket = TCPSocket.new('localhost', @intranet.port)
355
- socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
356
- expect(socket.gets).to include('HTTP/1.1 307 Temporary Redirect')
357
- while (line = socket.gets.chomp) # search the HTTP response for the 'Location' header
358
- break if line.start_with?('Location:')
359
- end
360
- expect(line).to include("http://localhost:#{@intranet.port}/responder/index.html")
361
- ensure
362
- socket.close
363
- Thread.kill(thread)
364
- thread.join
337
+ thread = Thread.new { @intranet.start }
338
+ while @intranet.instance_variable_get(:@server).status != :Running
365
339
  end
340
+
341
+ @intranet.stop
342
+ thread&.join
366
343
  end
367
344
  end
368
- end
369
345
 
370
- describe '#stop' do
371
- it 'should stop the web server and finalize all registered responders' do
372
- begin
346
+ context 'given an absolute URL' do
347
+ it 'should set up a redirection from /index.html to the provided URL' do
373
348
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
374
- responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', 'CONTENT'])
375
- @intranet.register_module(responder, %w[r], responder.resources_dir)
376
- thread = Thread.new do
377
- @intranet.start
378
- end
349
+ @intranet.home_url = '/responder/index.html'
350
+ thread = Thread.new { @intranet.start }
379
351
  while @intranet.instance_variable_get(:@server).status != :Running
380
352
  end
381
353
 
382
354
  socket = TCPSocket.new('localhost', @intranet.port)
383
- socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
384
- expect(socket.gets).to include('HTTP/1.1 200 OK')
385
- expect(responder.finalized).to be false
355
+ socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
356
+ expect(socket.gets).to include('HTTP/1.1 307 Temporary Redirect')
357
+ while (line = socket.gets.chomp) # search the HTTP response for the 'Location' header
358
+ break if line.start_with?('Location:')
359
+ end
360
+ expect(line).to include("http://localhost:#{@intranet.port}/responder/index.html")
386
361
  socket.close
387
362
 
388
363
  @intranet.stop
389
- while @intranet.instance_variable_get(:@server).status != :Stop
390
- end
364
+ thread&.join
365
+ end
366
+ end
367
+ end
391
368
 
392
- expect { TCPSocket.new('localhost', @intranet.port) }.to raise_error(Errno::ECONNREFUSED)
393
- expect(responder.finalized).to be true
394
- ensure
395
- socket.close
396
- Thread.kill(thread)
397
- thread.join
369
+ describe '#stop' do
370
+ it 'should stop the web server and finalize all registered responders' do
371
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
372
+ responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', 'CONTENT'])
373
+ @intranet.register_module(responder, %w[r], responder.resources_dir)
374
+ thread = Thread.new { @intranet.start }
375
+ while @intranet.instance_variable_get(:@server).status != :Running
398
376
  end
377
+
378
+ socket = TCPSocket.new('localhost', @intranet.port)
379
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
380
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
381
+ expect(responder.finalized).to be false
382
+ socket.close
383
+
384
+ @intranet.stop
385
+ thread&.join
386
+
387
+ expect { TCPSocket.new('localhost', @intranet.port) }.to raise_error(Errno::ECONNREFUSED)
388
+ expect(responder.finalized).to be true
399
389
  end
400
390
  end
401
391
  end
@@ -38,12 +38,12 @@ module Intranet
38
38
  end
39
39
 
40
40
  def resources_dir
41
- __dir__
41
+ super
42
42
  end
43
43
 
44
44
  def generate_page(path, query)
45
- if path == '/query'
46
- [200, 'text/plain', query.to_s + "\r\n"]
45
+ if path.start_with?('/query')
46
+ [200, 'text/plain', dump_with_encoding(path, query)]
47
47
  else
48
48
  @responses.fetch(path)
49
49
  end
@@ -58,5 +58,13 @@ module Intranet
58
58
  def js_dependencies
59
59
  super + @extra_js
60
60
  end
61
+
62
+ private
63
+
64
+ def dump_with_encoding(path, query)
65
+ "PATH=#{path} (#{path.encoding}), QUERY={" +
66
+ query.map { |k, v| "#{k} (#{k.encoding}) => #{v} (#{v.encoding})" }.join(',') +
67
+ "}\r\n"
68
+ end
61
69
  end
62
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intranet-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ebling Mis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-10 00:00:00.000000000 Z
11
+ date: 2021-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: haml
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.63.0
117
+ version: 0.81.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.63.0
124
+ version: 0.81.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -158,6 +158,7 @@ extra_rdoc_files: []
158
158
  files:
159
159
  - README.md
160
160
  - lib/core_extensions.rb
161
+ - lib/core_extensions/hash.rb
161
162
  - lib/core_extensions/string.rb
162
163
  - lib/core_extensions/tree.rb
163
164
  - lib/core_extensions/webrick/httpresponse.rb
@@ -179,6 +180,7 @@ files:
179
180
  - lib/intranet/resources/www/fonts/SourceSansPro.woff2
180
181
  - lib/intranet/resources/www/nav.js
181
182
  - lib/intranet/resources/www/style.css
183
+ - spec/core_extensions/hash_spec.rb
182
184
  - spec/core_extensions/string_spec.rb
183
185
  - spec/core_extensions/tree_spec.rb
184
186
  - spec/core_extensions/webrick/httpresponse_spec.rb
@@ -189,7 +191,6 @@ files:
189
191
  - spec/intranet/logger_spec.rb
190
192
  - spec/spec_helper.rb
191
193
  - spec/test_responder/responder.rb
192
- - spec/test_responder/www/style.css
193
194
  homepage: https://rubygems.org/gems/intranet-core
194
195
  licenses:
195
196
  - MIT
@@ -211,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
212
  version: '0'
212
213
  requirements: []
213
214
  rubyforge_project:
214
- rubygems_version: 2.5.2.1
215
+ rubygems_version: 2.7.6.2
215
216
  signing_key:
216
217
  specification_version: 4
217
218
  summary: Core component to build a custom intranet.
@@ -1,5 +0,0 @@
1
- /**
2
- * test_responder/www/style.css
3
- * Design for the Test Responder of the Intranet-Core.
4
- */
5
-