intranet-core 2.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
-