[privacy] remove Community Chatwoot Hub egress
Remove Community Chatwoot Hub URL/push relay/sync/registration/event/changelog egress. changelog.js becomes a local empty-feed adapter (no axios/fetch/network) exporting the default ChangelogApi; links.js drops the Hub changelog URL. lib/chatwoot_hub.rb removes base_url/push_notification_url/billing_base_url/ instance_config/send_push/send_push_with_response; billing_url reads only an explicit CHATWOOT_BILLING_URL env, HTTPS-only with host and no userinfo, and never falls back to a Hub URL. Enterprise proprietary base_url override is preserved (spec uses singleton_class.instance_methods(false) for edition-safety). privacy_audit uses a narrow per-file/per-rule Enterprise exception (hub-url only) and privacy_audit_test.sh proves forbidden Enterprise runtime lines are still detected; deployment privacy guard unchanged. Approved by independent five-key review deleg_49d6ee2e (passed=true, blocking arrays empty).
This commit is contained in:
@@ -1,15 +1,8 @@
|
|||||||
import axios from 'axios';
|
class ChangelogApi {
|
||||||
import ApiClient from './ApiClient';
|
// Keep the legacy API surface, but never fetch a vendor-owned changelog.
|
||||||
import { CHANGELOG_API_URL } from 'shared/constants/links';
|
|
||||||
|
|
||||||
class ChangelogApi extends ApiClient {
|
|
||||||
constructor() {
|
|
||||||
super('changelog', { apiVersion: 'v1' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
fetchFromHub() {
|
fetchFromHub() {
|
||||||
return axios.get(CHANGELOG_API_URL);
|
return Promise.resolve({ data: { posts: [] } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
app/javascript/dashboard/api/specs/changelog.spec.js
Normal file
24
app/javascript/dashboard/api/specs/changelog.spec.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import changelogAPI from '../changelog';
|
||||||
|
|
||||||
|
vi.mock('axios');
|
||||||
|
|
||||||
|
describe('#changelogAPI', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({}));
|
||||||
|
axios.get.mockResolvedValue({ data: { posts: [] } });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty local feed without making a request', async () => {
|
||||||
|
const response = await changelogAPI.fetchFromHub();
|
||||||
|
|
||||||
|
expect(response).toEqual({ data: { posts: [] } });
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
expect(axios.get).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,5 +7,3 @@ export const REPLY_POLICY = {
|
|||||||
'https://business.whatsapp.com/policy#:~:text=You%20may%20reply%20to%20a,messages%20via%20approved%20Message%20Templates.',
|
'https://business.whatsapp.com/policy#:~:text=You%20may%20reply%20to%20a,messages%20via%20approved%20Message%20Templates.',
|
||||||
TIKTOK: 'https://business-api.tiktok.com/portal/docs?id=1832184236919810',
|
TIKTOK: 'https://business-api.tiktok.com/portal/docs?id=1832184236919810',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG_API_URL = 'https://hub.2.chatwoot.com/changelogs';
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-n-slate-12 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
<% if (billing_url = ChatwootHub.billing_url).present? %>
|
||||||
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l-2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
<a href="<%= billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-n-slate-12 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
||||||
<span class="px-1">Upgrade now</span>
|
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l-2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
||||||
</a>
|
<span class="px-1">Upgrade now</span>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
|||||||
@@ -47,12 +47,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
|
<p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
|
||||||
</div>
|
</div>
|
||||||
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer">
|
<% if (billing_url = ChatwootHub.billing_url).present? %>
|
||||||
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
<a href="<%= billing_url %>" target="_blank" rel="noopener noreferrer">
|
||||||
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
||||||
<span class="px-1">Manage</span>
|
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||||
</button>
|
<span class="px-1">Manage</span>
|
||||||
</a>
|
</button>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,12 @@
|
|||||||
|
require 'uri'
|
||||||
|
|
||||||
class ChatwootHub
|
class ChatwootHub
|
||||||
DEFAULT_BASE_URL = 'https://hub.2.chatwoot.com'.freeze
|
|
||||||
|
|
||||||
def self.base_url
|
|
||||||
DEFAULT_BASE_URL
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.push_notification_url
|
|
||||||
"#{base_url}/send_push"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.billing_base_url
|
|
||||||
"#{base_url}/billing"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.installation_identifier
|
def self.installation_identifier
|
||||||
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
|
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
|
||||||
identifier ||= InstallationConfig.create!(name: 'INSTALLATION_IDENTIFIER', value: SecureRandom.uuid).value
|
identifier ||= InstallationConfig.create!(name: 'INSTALLATION_IDENTIFIER', value: SecureRandom.uuid).value
|
||||||
identifier
|
identifier
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.billing_url
|
|
||||||
"#{billing_base_url}?installation_identifier=#{installation_identifier}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pricing_plan
|
def self.pricing_plan
|
||||||
return 'community' unless ChatwootApp.enterprise?
|
return 'community' unless ChatwootApp.enterprise?
|
||||||
|
|
||||||
@@ -35,6 +19,18 @@ class ChatwootHub
|
|||||||
InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN_QUANTITY')&.value || 0
|
InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN_QUANTITY')&.value || 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.billing_url
|
||||||
|
configured_url = ENV['CHATWOOT_BILLING_URL'].to_s
|
||||||
|
return if configured_url.empty?
|
||||||
|
|
||||||
|
uri = URI.parse(configured_url)
|
||||||
|
return unless uri.scheme == 'https' && uri.host.present? && uri.user.nil? && uri.password.nil?
|
||||||
|
|
||||||
|
configured_url
|
||||||
|
rescue URI::InvalidURIError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def self.support_config
|
def self.support_config
|
||||||
{
|
{
|
||||||
support_website_token: InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_WEBSITE_TOKEN')&.value,
|
support_website_token: InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_WEBSITE_TOKEN')&.value,
|
||||||
@@ -43,28 +39,6 @@ class ChatwootHub
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.instance_config
|
|
||||||
{
|
|
||||||
installation_identifier: installation_identifier,
|
|
||||||
installation_version: Chatwoot.config[:version],
|
|
||||||
installation_host: URI.parse(ENV.fetch('FRONTEND_URL', '')).host,
|
|
||||||
installation_env: ENV.fetch('INSTALLATION_ENV', ''),
|
|
||||||
edition: ENV.fetch('CW_EDITION', '')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.send_push(fcm_options)
|
|
||||||
send_push_with_response(fcm_options)
|
|
||||||
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
|
|
||||||
Rails.logger.error "Exception: #{e.message}"
|
|
||||||
rescue StandardError => e
|
|
||||||
ChatwootExceptionTracker.new(e).capture_exception
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.send_push_with_response(fcm_options)
|
|
||||||
info = { fcm_options: fcm_options }
|
|
||||||
RestClient.post(push_notification_url, info.merge(instance_config).to_json, { content_type: :json, accept: :json })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ChatwootHub.singleton_class.prepend_mod_with('ChatwootHub')
|
ChatwootHub.singleton_class.prepend_mod_with('ChatwootHub')
|
||||||
|
|||||||
@@ -33,7 +33,27 @@ VISIBLE_BRANDING = re.compile(r"\bChatwoot\b")
|
|||||||
VISIBLE_ROOTS = ("app/views", "app/javascript", "config/locales", "public")
|
VISIBLE_ROOTS = ("app/views", "app/javascript", "config/locales", "public")
|
||||||
BUILT_ROOTS = ("public/assets", "public/packs", "public/vite")
|
BUILT_ROOTS = ("public/assets", "public/packs", "public/vite")
|
||||||
SKIP_PARTS = {".git", "node_modules", ".pnpm-store", "tmp", "log", "coverage", "storage"}
|
SKIP_PARTS = {".git", "node_modules", ".pnpm-store", "tmp", "log", "coverage", "storage"}
|
||||||
DEFAULT_ALLOWED_PATHS = {"LICENSE", "script/privacy_audit", "script/privacy_audit_test.sh"}
|
DEFAULT_ALLOWED_PATHS = {
|
||||||
|
"LICENSE",
|
||||||
|
"script/privacy_audit",
|
||||||
|
"script/privacy_audit_test.sh",
|
||||||
|
"deployment/spec/setup_20.04_privacy_test.sh",
|
||||||
|
# Historical planning/evidence files may quote removed endpoints or SDKs;
|
||||||
|
# runtime source and built artifacts are never covered by this exception.
|
||||||
|
".hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md",
|
||||||
|
".hermes/plans/chatwoot-private/06-remove-amplitude.md",
|
||||||
|
# The log records removed symbols; Enterprise retains proprietary Hub code
|
||||||
|
# outside the Community release boundary and is reviewed separately.
|
||||||
|
"engineering-log.md",
|
||||||
|
"spec/enterprise/lib/chatwoot_hub_spec.rb",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enterprise keeps a proprietary Hub boundary outside the Community release.
|
||||||
|
# Allow only the known boundary URL; all other privacy rules still scan this
|
||||||
|
# runtime file so adding a forbidden SDK or event reporter cannot be hidden.
|
||||||
|
DEFAULT_ALLOWED_RULES = {
|
||||||
|
"enterprise/lib/enterprise/chatwoot_hub.rb": {"hub-url"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
@@ -176,12 +196,13 @@ def main() -> int:
|
|||||||
text = read_text(path)
|
text = read_text(path)
|
||||||
if text is None:
|
if text is None:
|
||||||
continue
|
continue
|
||||||
allowed = relative in allowlist
|
allowed_path = relative in allowlist
|
||||||
|
allowed_rules = DEFAULT_ALLOWED_RULES.get(relative, set())
|
||||||
|
|
||||||
for line_number, line in enumerate(text.splitlines(), start=1):
|
for line_number, line in enumerate(text.splitlines(), start=1):
|
||||||
for category, pattern in RULES:
|
for category, pattern in RULES:
|
||||||
if pattern.search(line):
|
if pattern.search(line):
|
||||||
if allowed:
|
if allowed_path or category in allowed_rules:
|
||||||
continue
|
continue
|
||||||
prefix = "REPORT" if args.report_only else "FAIL"
|
prefix = "REPORT" if args.report_only else "FAIL"
|
||||||
print(f"{prefix} {relative}:{line_number}:{category}")
|
print(f"{prefix} {relative}:{line_number}:{category}")
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ ROOT=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-test.XXXXXX")
|
|||||||
OUTSIDE=$(mktemp "${TMPDIR:-/tmp}/privacy-audit-outside.XXXXXX")
|
OUTSIDE=$(mktemp "${TMPDIR:-/tmp}/privacy-audit-outside.XXXXXX")
|
||||||
OUTSIDE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-outside-dir.XXXXXX")
|
OUTSIDE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-outside-dir.XXXXXX")
|
||||||
TRACKED_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-tracked.XXXXXX")
|
TRACKED_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-tracked.XXXXXX")
|
||||||
trap 'rm -rf "$ROOT" "$OUTSIDE" "$OUTSIDE_DIR" "$TRACKED_ROOT"' EXIT
|
ENTERPRISE_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/privacy-audit-enterprise.XXXXXX")
|
||||||
|
trap 'rm -rf "$ROOT" "$OUTSIDE" "$OUTSIDE_DIR" "$TRACKED_ROOT" "$ENTERPRISE_ROOT"' EXIT
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
printf 'FAIL: %s\n' "$1" >&2
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
@@ -33,6 +34,18 @@ printf '%s\n' 'docs/audit-record.md' > "$ROOT/allowlist.txt"
|
|||||||
printf '%s\n' 'https://hub.2.chatwoot.com/ping' > "$ROOT/docs/audit-record.md"
|
printf '%s\n' 'https://hub.2.chatwoot.com/ping' > "$ROOT/docs/audit-record.md"
|
||||||
python3 "$AUDIT" --root "$ROOT" --allowlist-file "$ROOT/allowlist.txt" >/dev/null || fail 'explicitly allowlisted audit document must pass'
|
python3 "$AUDIT" --root "$ROOT" --allowlist-file "$ROOT/allowlist.txt" >/dev/null || fail 'explicitly allowlisted audit document must pass'
|
||||||
|
|
||||||
|
mkdir -p "$ENTERPRISE_ROOT/enterprise/lib/enterprise"
|
||||||
|
git -C "$ENTERPRISE_ROOT" init -q
|
||||||
|
printf '%s\n%s\n' 'https://hub.2.chatwoot.com/ping' "import '@sentry/vue'" > "$ENTERPRISE_ROOT/enterprise/lib/enterprise/chatwoot_hub.rb"
|
||||||
|
git -C "$ENTERPRISE_ROOT" add enterprise/lib/enterprise/chatwoot_hub.rb
|
||||||
|
set +e
|
||||||
|
enterprise_output=$(python3 "$AUDIT" --root "$ENTERPRISE_ROOT" 2>&1)
|
||||||
|
enterprise_exit=$?
|
||||||
|
set -e
|
||||||
|
[ "$enterprise_exit" -ne 0 ] || fail 'enterprise runtime must not be exempt from every privacy rule'
|
||||||
|
printf '%s\n' "$enterprise_output" | grep -F 'enterprise/lib/enterprise/chatwoot_hub.rb:2:sentry-sdk' >/dev/null || fail 'enterprise runtime Sentry finding was not reported'
|
||||||
|
printf '%s\n' "$enterprise_output" | grep -F 'enterprise/lib/enterprise/chatwoot_hub.rb:1:hub-url' >/dev/null && fail 'intentional enterprise Hub boundary was not narrowly allowlisted'
|
||||||
|
|
||||||
printf '%s\n' 'https://hub.2.chatwoot.com/ping' > "$OUTSIDE"
|
printf '%s\n' 'https://hub.2.chatwoot.com/ping' > "$OUTSIDE"
|
||||||
set +e
|
set +e
|
||||||
outside_allowlist_status=$(python3 "$AUDIT" --root "$ROOT" --allowlist-file "$OUTSIDE" >/dev/null 2>&1)
|
outside_allowlist_status=$(python3 "$AUDIT" --root "$ROOT" --allowlist-file "$OUTSIDE" >/dev/null 2>&1)
|
||||||
|
|||||||
@@ -10,4 +10,12 @@ RSpec.describe SuperAdmin::SettingsController do
|
|||||||
|
|
||||||
expect(template).not_to include('refresh_super_admin_settings_url')
|
expect(template).not_to include('refresh_super_admin_settings_url')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'renders billing links only when an explicit validated URL exists' do
|
||||||
|
settings_template = Rails.root.join('app/views/super_admin/settings/show.html.erb').read
|
||||||
|
upgrade_template = Rails.root.join('app/views/super_admin/settings/_upgrade_button_enterprise.html.erb').read
|
||||||
|
|
||||||
|
expect(settings_template).to include('(billing_url = ChatwootHub.billing_url).present?')
|
||||||
|
expect(upgrade_template).to include('(billing_url = ChatwootHub.billing_url).present?')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,15 +7,53 @@ describe ChatwootHub do
|
|||||||
expect(described_class).not_to respond_to(:emit_event)
|
expect(described_class).not_to respond_to(:emit_event)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'retains push and instance compatibility APIs for the push-relay task' do
|
it 'does not expose remote URL constructors or a push relay' do
|
||||||
expect(described_class).to respond_to(:push_notification_url, :send_push, :send_push_with_response, :instance_config)
|
expect(described_class).not_to respond_to(
|
||||||
expect(described_class.push_notification_url).to eq('https://hub.2.chatwoot.com/send_push')
|
:billing_base_url,
|
||||||
|
:push_notification_url,
|
||||||
|
:send_push,
|
||||||
|
:send_push_with_response,
|
||||||
|
:instance_config
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enterprise may prepend its proprietary base_url compatibility method.
|
||||||
|
# Check only methods defined directly by the Community singleton class so
|
||||||
|
# the shared spec remains valid in both editions.
|
||||||
|
expect(described_class.singleton_class.instance_methods(false)).not_to include(:base_url)
|
||||||
|
|
||||||
|
expect(described_class).to respond_to(:billing_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.base_url' do
|
it 'keeps pricing reads local to the installation' do
|
||||||
it 'uses the static hub url' do
|
expect(described_class.pricing_plan).to eq('community')
|
||||||
expect(described_class::DEFAULT_BASE_URL).to eq('https://hub.2.chatwoot.com')
|
expect(described_class.pricing_plan_quantity).to eq(0)
|
||||||
expect(described_class.base_url).to eq('https://hub.2.chatwoot.com')
|
end
|
||||||
|
|
||||||
|
describe '.billing_url' do
|
||||||
|
around do |example|
|
||||||
|
previous = ENV['CHATWOOT_BILLING_URL']
|
||||||
|
example.run
|
||||||
|
ENV['CHATWOOT_BILLING_URL'] = previous
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns no link unless an administrator explicitly configures one' do
|
||||||
|
ENV.delete('CHATWOOT_BILLING_URL')
|
||||||
|
|
||||||
|
expect(described_class.billing_url).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts an explicitly configured HTTPS link' do
|
||||||
|
ENV['CHATWOOT_BILLING_URL'] = 'https://billing.example.test/manage'
|
||||||
|
|
||||||
|
expect(described_class.billing_url).to eq('https://billing.example.test/manage')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rejects non-HTTPS and malformed links' do
|
||||||
|
ENV['CHATWOOT_BILLING_URL'] = 'http://billing.example.test/manage'
|
||||||
|
expect(described_class.billing_url).to be_nil
|
||||||
|
|
||||||
|
ENV['CHATWOOT_BILLING_URL'] = 'not-a-url'
|
||||||
|
expect(described_class.billing_url).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user