mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
80244ea4e2
Squash of feat/koreader-widget-board. KOReader plugin 2.0.0 gets the widget-board dashboard, redesigned book details, menus, lists and dialogs, Decor page and reading companions. Highlights, notes and bookmarks now stay in step across web, Kobo, KOReader and the phones (defects D-00 to D-20, migration 0121), and reading progress and narration hand off cleanly between devices.
521 lines
21 KiB
Lua
521 lines
21 KiB
Lua
local mock_http_body = "{}"
|
|
local mock_http_code = 200
|
|
local request_ran_in_subprocess = false
|
|
local in_subprocess = false
|
|
local encoded_subprocess_result
|
|
|
|
package.loaded["logger"] = {
|
|
dbg = function() end,
|
|
warn = function() end,
|
|
}
|
|
|
|
package.loaded["util"] = {
|
|
trim = function(value)
|
|
return tostring(value or ""):match("^%s*(.-)%s*$")
|
|
end,
|
|
fixUtf8 = function(value)
|
|
return value
|
|
end,
|
|
urlEncode = function(value)
|
|
return tostring(value)
|
|
end,
|
|
removeFile = function() end,
|
|
}
|
|
|
|
package.loaded["socketutil"] = {
|
|
LARGE_BLOCK_TIMEOUT = 1,
|
|
LARGE_TOTAL_TIMEOUT = 1,
|
|
FILE_BLOCK_TIMEOUT = 1,
|
|
FILE_TOTAL_TIMEOUT = 1,
|
|
set_timeout = function() end,
|
|
reset_timeout = function() end,
|
|
}
|
|
|
|
package.loaded["socket"] = {
|
|
skip = function(n, ...)
|
|
local values = { ... }
|
|
local shifted = {}
|
|
for index = n + 1, #values do
|
|
table.insert(shifted, values[index])
|
|
end
|
|
return unpack(shifted)
|
|
end,
|
|
}
|
|
|
|
package.loaded["ltn12"] = {
|
|
sink = {
|
|
table = function(target)
|
|
return function(chunk)
|
|
if chunk then table.insert(target, chunk) end
|
|
return 1
|
|
end
|
|
end,
|
|
file = function(file)
|
|
return function(chunk)
|
|
if chunk then file:write(chunk) else file:close() end
|
|
return 1
|
|
end
|
|
end,
|
|
},
|
|
source = {
|
|
string = function(value)
|
|
local pending = value
|
|
return function()
|
|
local chunk = pending
|
|
pending = nil
|
|
return chunk
|
|
end
|
|
end,
|
|
},
|
|
}
|
|
|
|
local last_request_url
|
|
local last_request_headers
|
|
local last_request_proxy
|
|
local last_global_proxy
|
|
local mock_http_error = false
|
|
local mock_http_queue
|
|
local request_history = {}
|
|
local request_count = 0
|
|
package.loaded["socket.http"] = {
|
|
request = function(request)
|
|
last_global_proxy = package.loaded["socket.http"].PROXY
|
|
if mock_http_error then error("simulated transport failure") end
|
|
request_count = request_count + 1
|
|
request_ran_in_subprocess = in_subprocess
|
|
last_request_url = request.url
|
|
last_request_headers = request.headers
|
|
last_request_proxy = request.proxy
|
|
table.insert(request_history, {
|
|
url = request.url,
|
|
proxy = request.proxy,
|
|
global_proxy = last_global_proxy,
|
|
redirect = request.redirect,
|
|
create = request.create,
|
|
})
|
|
if mock_http_queue then
|
|
local response = table.remove(mock_http_queue, 1)
|
|
if response.body then request.sink(response.body) end
|
|
return 1, response.code, response.headers or {}, "HTTP " .. tostring(response.code)
|
|
end
|
|
if mock_http_body then
|
|
request.sink(mock_http_body)
|
|
end
|
|
return 1, mock_http_code, {}, "HTTP " .. tostring(mock_http_code)
|
|
end,
|
|
}
|
|
|
|
local rapidjson_null = {}
|
|
local last_encoded_value
|
|
package.loaded["rapidjson"] = {
|
|
null = rapidjson_null,
|
|
encode = function(value)
|
|
if type(value) == "table"
|
|
and (value.body ~= nil or value.err ~= nil or value.errbody ~= nil) then
|
|
encoded_subprocess_result = value
|
|
return "__subprocess_result__"
|
|
end
|
|
last_encoded_value = value
|
|
return "{}"
|
|
end,
|
|
decode = function(raw)
|
|
if raw == "{}" then return {} end
|
|
if raw == "__subprocess_result__" then return encoded_subprocess_result end
|
|
if raw == "{\"ok\":true}" then return { ok = true } end
|
|
if raw == "{\"value\":null}" then return { value = rapidjson_null } end
|
|
if raw == "null" then return rapidjson_null end
|
|
return nil, "parse error"
|
|
end,
|
|
}
|
|
|
|
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
|
|
|
|
local BookOrbitApi = require("bookorbit_api")
|
|
|
|
local function assertEqual(actual, expected, label)
|
|
if actual ~= expected then
|
|
error(string.format("%s: expected %s, got %s", label, tostring(expected), tostring(actual)))
|
|
end
|
|
end
|
|
|
|
assertEqual(BookOrbitApi.normalizeServerUrl("https://bookorbit.example.com"),
|
|
"https://bookorbit.example.com/api/v1", "origin-only server URL is normalized")
|
|
assertEqual(BookOrbitApi.normalizeServerUrl("https://bookorbit.example.com/api/v1/koreader"),
|
|
"https://bookorbit.example.com/api/v1", "stock sync URL is normalized")
|
|
assertEqual(BookOrbitApi.normalizeServerUrl(" http://127.0.0.1:6262/api/v1/ "),
|
|
"http://127.0.0.1:6262/api/v1", "local server URL trims whitespace and trailing slash")
|
|
assertEqual(BookOrbitApi.normalizeServerUrl("https://[::1]:443/library"),
|
|
"https://[::1]:443/library/api/v1", "IPv6 and deployment subpath remain supported")
|
|
for _, value in ipairs({ "not a url", "https://", "ftp://bookorbit.example.com", "http://host:0",
|
|
"http://host:65536", "https://user:secret@host", "https://host?token=test", "https://host#part" }) do
|
|
assertEqual(BookOrbitApi.normalizeServerUrl(value), nil, "malformed server URL is rejected")
|
|
end
|
|
|
|
local decoded, err = BookOrbitApi.decodeResponse({ "" })
|
|
assertEqual(type(decoded), "table", "empty body decodes to table")
|
|
assertEqual(err, nil, "empty body has no error")
|
|
|
|
decoded, err = BookOrbitApi.decodeResponse({ "{\"value\":null}" })
|
|
assertEqual(decoded.value, nil, "JSON null is scrubbed")
|
|
assertEqual(err, nil, "valid JSON has no error")
|
|
|
|
decoded, err = BookOrbitApi.decodeResponse({ "not-json" })
|
|
assertEqual(decoded, nil, "invalid JSON has no decoded body")
|
|
assertEqual(err, "invalid_json", "invalid JSON error code")
|
|
|
|
local client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
}
|
|
|
|
local filtered_client = BookOrbitApi.new{ server_url = "https://bookorbit.example.com" }
|
|
local captured_path
|
|
filtered_client.request = function(_, method, path) captured_path = path end
|
|
local local_query = { ids = "1,2", sort = "title", on_device_filter = true }
|
|
filtered_client:catalogBooks(local_query)
|
|
assertEqual(captured_path:find("on_device_filter", 1, true), nil, "local device context never reaches server validation")
|
|
assertEqual(captured_path:find("ids=1,2", 1, true) ~= nil, true, "device IDs remain in request")
|
|
assertEqual(local_query.on_device_filter, true, "request does not mutate local context")
|
|
filtered_client:catalogManifest(local_query)
|
|
assertEqual(captured_path:find("on_device_filter", 1, true), nil, "bulk manifest also excludes local device context")
|
|
|
|
local loopback_client = BookOrbitApi.new{
|
|
server_url = "http://localhost:3000/api/v1",
|
|
}
|
|
assertEqual(loopback_client.server_url, "http://127.0.0.1:3000/api/v1",
|
|
"HTTP localhost is made safe before a macOS subprocess fork")
|
|
|
|
local secure_loopback_client = BookOrbitApi.new{
|
|
server_url = "https://localhost:3443/api/v1",
|
|
}
|
|
assertEqual(secure_loopback_client.server_url, "https://localhost:3443/api/v1",
|
|
"HTTPS localhost retains its certificate hostname")
|
|
|
|
mock_http_body = "{\"ok\":true}"
|
|
mock_http_code = 200
|
|
local body
|
|
body, err = client:auth()
|
|
assertEqual(body.ok, true, "request returns decoded body")
|
|
assertEqual(err, nil, "valid request has no error")
|
|
|
|
mock_http_body = "not-json"
|
|
mock_http_code = 200
|
|
body, err = client:auth()
|
|
assertEqual(body, nil, "invalid success body has no decoded body")
|
|
assertEqual(err, "invalid_json", "invalid success body returns invalid_json")
|
|
|
|
mock_http_body = "not-json"
|
|
mock_http_code = 503
|
|
local errbody
|
|
body, err, errbody = client:auth()
|
|
assertEqual(body, nil, "HTTP error has no decoded body")
|
|
assertEqual(err, 503, "HTTP error preserves status code")
|
|
assertEqual(errbody, nil, "invalid HTTP error body is ignored")
|
|
|
|
mock_http_body = "{\"ok\":true}"
|
|
mock_http_code = 200
|
|
client:catalogDashboardSection("up-next-in-series")
|
|
assertEqual(last_request_url,
|
|
"https://bookorbit.example.com/api/v1/koreader/plugin/catalog/dashboard/sections/up-next-in-series",
|
|
"the dashboard-section endpoint is addressed by source type")
|
|
|
|
client:getPluginVersion()
|
|
assertEqual(last_request_headers["x-bookorbit-device-id"], nil,
|
|
"a legacy client sends no partial self-update identity")
|
|
assertEqual(last_request_headers["x-bookorbit-plugin-version"], nil,
|
|
"a legacy client sends no partial self-update version")
|
|
|
|
local partial_identity_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
device_id = "device-id",
|
|
}
|
|
partial_identity_client:getPluginVersion()
|
|
assertEqual(last_request_headers["x-bookorbit-device-id"], nil,
|
|
"an incomplete identity sends no device header")
|
|
assertEqual(last_request_headers["x-bookorbit-plugin-version"], nil,
|
|
"an incomplete identity sends no version header")
|
|
|
|
local unsafe_identity_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
device_id = "device-id\r\ninjected",
|
|
plugin_version = "1.5.4",
|
|
}
|
|
unsafe_identity_client:getPluginVersion()
|
|
assertEqual(last_request_headers["x-bookorbit-device-id"], nil,
|
|
"an unsafe device id sends no identity headers")
|
|
assertEqual(last_request_headers["x-bookorbit-plugin-version"], nil,
|
|
"an unsafe device id sends no version header")
|
|
|
|
local match_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
device_id = "device-id",
|
|
device_model = "Test device",
|
|
plugin_version = "1.5.1",
|
|
}
|
|
local hash_a = string.rep("a", 32)
|
|
local hash_b = string.rep("B", 32)
|
|
local invalid_hash = "not-an-md5"
|
|
local oversized_title = string.rep("t", 501)
|
|
local oversized_authors = string.rep("a", 1001)
|
|
|
|
match_client:getPluginVersion()
|
|
assertEqual(last_request_headers["x-bookorbit-device-id"], "device-id",
|
|
"the update check identifies the requesting device")
|
|
assertEqual(last_request_headers["x-bookorbit-plugin-version"], "1.5.1",
|
|
"the update check reports the requesting plugin version")
|
|
|
|
mock_http_body = "{\"ok\":true}"
|
|
mock_http_code = 200
|
|
body, err = match_client:matchCheck({ hash_a, invalid_hash, hash_b }, {
|
|
[hash_a] = {
|
|
title = oversized_title,
|
|
authors = oversized_authors,
|
|
last_open = -1,
|
|
source = "invalid",
|
|
metadata_ambiguous = "yes",
|
|
},
|
|
[hash_b] = {
|
|
title = 123,
|
|
authors = {},
|
|
last_open = "123",
|
|
source = "file",
|
|
metadata_ambiguous = false,
|
|
},
|
|
})
|
|
assertEqual(body.ok, true, "a match-check with one invalid row still reaches the server")
|
|
assertEqual(err, nil, "a sanitized match-check succeeds")
|
|
assertEqual(#last_encoded_value.hashes, 2, "an invalid hash is removed without dropping valid peers")
|
|
assertEqual(last_encoded_value.hashes[1], hash_a, "the first valid hash keeps its position")
|
|
assertEqual(last_encoded_value.hashes[2], hash_b, "the later valid hash closes the filtered gap")
|
|
assertEqual(#last_encoded_value.books, 2, "candidate metadata stays aligned with filtered hashes")
|
|
assertEqual(#last_encoded_value.books[1].title, 500, "an oversized title is bounded to the server contract")
|
|
assertEqual(#last_encoded_value.books[1].authors, 1000, "oversized authors are bounded to the server contract")
|
|
assertEqual(last_encoded_value.books[1].lastOpen, nil, "a negative timestamp is omitted")
|
|
assertEqual(last_encoded_value.books[1].source, nil, "an unknown source is omitted")
|
|
assertEqual(last_encoded_value.books[1].metadataAmbiguous, nil, "a non-boolean ambiguity flag is omitted")
|
|
assertEqual(last_encoded_value.books[2].title, nil, "non-string titles are omitted")
|
|
assertEqual(last_encoded_value.books[2].authors, nil, "non-string authors are omitted")
|
|
assertEqual(last_encoded_value.books[2].lastOpen, 123, "a numeric timestamp string is normalized")
|
|
assertEqual(last_encoded_value.books[2].source, "file", "a valid source is preserved")
|
|
assertEqual(last_encoded_value.books[2].metadataAmbiguous, false, "a boolean ambiguity flag is preserved")
|
|
|
|
local requests_before_empty_match = request_count
|
|
body, err = match_client:matchCheck({ invalid_hash }, { [invalid_hash] = { title = "Ignored" } })
|
|
assertEqual(type(body), "table", "an all-invalid batch resolves locally")
|
|
assertEqual(#body.matches, 0, "an all-invalid batch has no matches")
|
|
assertEqual(err, nil, "an all-invalid batch is not reported as a network error")
|
|
assertEqual(request_count, requests_before_empty_match, "an empty sanitized batch is not sent to the server")
|
|
|
|
local wrapped = true
|
|
local subprocess_calls = 0
|
|
local subprocess_result_mode = "normal"
|
|
package.loaded["ui/trapper"] = {
|
|
isWrapped = function()
|
|
return wrapped
|
|
end,
|
|
dismissableRunInSubprocess = function(_, task, trap_widget, task_returns_simple_string)
|
|
assertEqual(type(trap_widget), "table", "background request uses a detached trap widget")
|
|
assertEqual(task_returns_simple_string, true, "background request uses a string result envelope")
|
|
subprocess_calls = subprocess_calls + 1
|
|
in_subprocess = true
|
|
local result = task()
|
|
in_subprocess = false
|
|
if subprocess_result_mode == "missing" then
|
|
return true
|
|
elseif subprocess_result_mode == "malformed" then
|
|
return true, "not-json"
|
|
end
|
|
return true, result
|
|
end,
|
|
}
|
|
|
|
local background_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
background_requests = true,
|
|
}
|
|
|
|
mock_http_body = "{\"ok\":true}"
|
|
mock_http_code = 200
|
|
body, err = background_client:auth()
|
|
assertEqual(body.ok, true, "background request returns decoded body")
|
|
assertEqual(err, nil, "background request preserves success result")
|
|
assertEqual(subprocess_calls, 1, "wrapped background request uses subprocess")
|
|
assertEqual(request_ran_in_subprocess, true, "HTTP request runs inside subprocess task")
|
|
|
|
local background_identity_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
device_id = "background-device",
|
|
plugin_version = "1.5.4",
|
|
background_requests = true,
|
|
}
|
|
body, err = background_identity_client:getPluginVersion()
|
|
assertEqual(body ~= nil, true, "background update check has a response body")
|
|
assertEqual(err, nil, "background update check succeeds")
|
|
assertEqual(subprocess_calls, 2, "background update check uses subprocess")
|
|
assertEqual(last_request_headers["x-bookorbit-device-id"], "background-device",
|
|
"the subprocess preserves the requesting device header")
|
|
assertEqual(last_request_headers["x-bookorbit-plugin-version"], "1.5.4",
|
|
"the subprocess preserves the requesting version header")
|
|
|
|
subprocess_result_mode = "missing"
|
|
body, err = background_client:auth()
|
|
assertEqual(body, nil, "missing subprocess payload has no response body")
|
|
assertEqual(err, "subprocess_no_result", "missing subprocess payload has a specific error")
|
|
|
|
subprocess_result_mode = "malformed"
|
|
body, err = background_client:auth()
|
|
assertEqual(body, nil, "malformed subprocess payload has no response body")
|
|
assertEqual(err, "subprocess_invalid_result", "malformed subprocess payload has a specific error")
|
|
|
|
subprocess_result_mode = "normal"
|
|
wrapped = false
|
|
request_ran_in_subprocess = false
|
|
body, err = background_client:auth()
|
|
assertEqual(body.ok, true, "unwrapped request falls back safely")
|
|
assertEqual(err, nil, "unwrapped request succeeds")
|
|
assertEqual(subprocess_calls, 4, "unwrapped request does not start subprocess")
|
|
assertEqual(request_ran_in_subprocess, false, "unwrapped fallback runs in current process")
|
|
|
|
-- Proxy resolution tests
|
|
assertEqual(client:getProxy(), nil, "default client has no proxy")
|
|
|
|
local explicit_proxy_client = BookOrbitApi.new{
|
|
server_url = "http://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
proxy = "http://127.0.0.1:8080",
|
|
}
|
|
assertEqual(explicit_proxy_client:getProxy(), "http://127.0.0.1:8080",
|
|
"explicit proxy is returned for HTTP server")
|
|
|
|
mock_http_body = "{\"ok\":true}"
|
|
mock_http_code = 200
|
|
explicit_proxy_client:auth()
|
|
assertEqual(last_request_proxy, "http://127.0.0.1:8080",
|
|
"explicit proxy is passed to request table for HTTP server")
|
|
|
|
local https_explicit_proxy_client = BookOrbitApi.new{
|
|
server_url = "https://bookorbit.example.com/api/v1",
|
|
proxy = "http://127.0.0.1:8080",
|
|
}
|
|
assertEqual(https_explicit_proxy_client:getProxy(), "http://127.0.0.1:8080",
|
|
"explicit proxy is returned for HTTPS server")
|
|
|
|
local mock_http = package.loaded["socket.http"]
|
|
mock_http.PROXY = "http://127.0.0.1:8080"
|
|
https_explicit_proxy_client:auth()
|
|
assertEqual(last_global_proxy, nil, "HTTPS request clears LuaSocket's global proxy")
|
|
assertEqual(last_request_proxy, nil, "HTTPS request does not send a proxy URL to LuaSocket")
|
|
assertEqual(type(request_history[#request_history].create), "function",
|
|
"HTTPS request uses a CONNECT socket")
|
|
assertEqual(mock_http.PROXY, "http://127.0.0.1:8080", "HTTPS request restores the global proxy")
|
|
|
|
mock_http_error = true
|
|
local _, transport_error = https_explicit_proxy_client:auth()
|
|
assertEqual(transport_error:find("simulated transport failure", 1, true) ~= nil, true,
|
|
"HTTPS transport failure is reported")
|
|
assertEqual(mock_http.PROXY, "http://127.0.0.1:8080",
|
|
"HTTPS transport failure restores the global proxy")
|
|
mock_http_error = false
|
|
mock_http.PROXY = nil
|
|
|
|
mock_http.PROXY = "http://127.0.0.1:8080"
|
|
mock_http_queue = {
|
|
{ code = 302, headers = { location = "https://attacker.example.com/api/v1" } },
|
|
}
|
|
local before_redirect = #request_history
|
|
local _, unsafe_redirect = https_explicit_proxy_client:auth()
|
|
assertEqual(unsafe_redirect, "unsafe_redirect", "cross-host API redirect is rejected")
|
|
assertEqual(#request_history, before_redirect + 1, "cross-host API redirect sends one request")
|
|
assertEqual(request_history[#request_history].redirect, false,
|
|
"LuaSocket cannot automatically forward API credentials")
|
|
assertEqual(request_history[#request_history].global_proxy, nil,
|
|
"HTTPS redirect response cannot use the global HTTP proxy")
|
|
mock_http_queue = {
|
|
{ code = 302, headers = { location = "/api/v1/redirected" } },
|
|
{ code = 200, body = "{\"ok\":true}" },
|
|
}
|
|
local same_origin_body, same_origin_error = https_explicit_proxy_client:auth()
|
|
assertEqual(same_origin_error, nil, "same-origin API redirect succeeds")
|
|
assertEqual(same_origin_body.ok, true, "same-origin API redirect decodes the final body")
|
|
assertEqual(request_history[#request_history].url,
|
|
"https://bookorbit.example.com/api/v1/redirected", "same-origin redirect uses the new URL")
|
|
assertEqual(request_history[#request_history].global_proxy, nil,
|
|
"same-origin HTTPS redirect stays off the global HTTP proxy")
|
|
mock_http_queue = {
|
|
{ code = 302, headers = { location = "?refresh=1" } },
|
|
{ code = 200, body = "{\"ok\":true}" },
|
|
}
|
|
https_explicit_proxy_client:auth()
|
|
assertEqual(request_history[#request_history].url,
|
|
"https://bookorbit.example.com/api/v1/koreader/users/auth?refresh=1",
|
|
"query-only redirect preserves the original path")
|
|
mock_http_queue = nil
|
|
mock_http.PROXY = nil
|
|
|
|
-- KOReader global settings proxy test
|
|
local http_client = BookOrbitApi.new{
|
|
server_url = "http://bookorbit.example.com/api/v1",
|
|
username = "reader",
|
|
userkey = "secret",
|
|
}
|
|
|
|
local mock_settings = {
|
|
http_proxy_enabled = true,
|
|
http_proxy = "http://192.168.1.1:3128",
|
|
}
|
|
G_reader_settings = {
|
|
isTrue = function(self, key) return mock_settings[key] == true end,
|
|
readSetting = function(self, key) return mock_settings[key] end,
|
|
}
|
|
|
|
assertEqual(http_client:getProxy(), "http://192.168.1.1:3128",
|
|
"G_reader_settings proxy is used when enabled for HTTP server")
|
|
|
|
http_client:auth()
|
|
assertEqual(last_request_proxy, "http://192.168.1.1:3128",
|
|
"G_reader_settings proxy is passed to request table")
|
|
assertEqual(last_global_proxy, nil, "HTTP requests do not change LuaSocket's global proxy")
|
|
|
|
mock_http.PROXY = "http://192.168.1.1:3128"
|
|
mock_http_queue = {
|
|
{ code = 301, headers = { location = "https://bookorbit.example.com/api/v1/redirected" } },
|
|
{ code = 200, body = "{\"ok\":true}" },
|
|
}
|
|
local upgraded_body, upgraded_error = http_client:auth()
|
|
assertEqual(upgraded_error, nil, "same-host HTTPS upgrade succeeds")
|
|
assertEqual(upgraded_body.ok, true, "same-host HTTPS upgrade decodes the final body")
|
|
assertEqual(request_history[#request_history - 1].proxy, "http://192.168.1.1:3128",
|
|
"HTTP upgrade starts through the configured proxy")
|
|
assertEqual(request_history[#request_history - 1].global_proxy, "http://192.168.1.1:3128",
|
|
"HTTP upgrade leaves KOReader's global proxy intact")
|
|
assertEqual(request_history[#request_history].proxy, nil,
|
|
"HTTPS upgrade keeps the proxy URL out of LuaSocket's request")
|
|
assertEqual(type(request_history[#request_history].create), "function",
|
|
"HTTPS upgrade uses a CONNECT socket")
|
|
assertEqual(request_history[#request_history].global_proxy, nil,
|
|
"HTTPS upgrade clears LuaSocket's global HTTP proxy")
|
|
assertEqual(mock_http.PROXY, "http://192.168.1.1:3128",
|
|
"HTTPS upgrade restores LuaSocket's global HTTP proxy")
|
|
mock_http_queue = nil
|
|
mock_http.PROXY = nil
|
|
|
|
mock_settings.http_proxy_enabled = false
|
|
assertEqual(http_client:getProxy(), nil,
|
|
"G_reader_settings proxy is ignored when disabled")
|
|
|
|
G_reader_settings = nil
|
|
|
|
print("bookorbit_api_test.lua: ok")
|