feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
import 'package:buzz/shared/deeplink/deep_link.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
_inviteTests();
|
||||
_buildMessageLinkTests();
|
||||
|
||||
group('parseMessageDeepLink', () {
|
||||
test('parses channel and id', () {
|
||||
final link = parseMessageDeepLink(
|
||||
Uri.parse('buzz://message?channel=d14cd131&id=abc123'),
|
||||
);
|
||||
expect(
|
||||
link,
|
||||
const MessageDeepLink(channelId: 'd14cd131', messageId: 'abc123'),
|
||||
);
|
||||
});
|
||||
|
||||
test('parses optional thread param', () {
|
||||
final link = parseMessageDeepLink(
|
||||
Uri.parse('buzz://message?channel=d14cd131&id=abc123&thread=root99'),
|
||||
);
|
||||
expect(link?.threadRootId, 'root99');
|
||||
});
|
||||
|
||||
test('treats empty thread as absent', () {
|
||||
final link = parseMessageDeepLink(
|
||||
Uri.parse('buzz://message?channel=d14cd131&id=abc123&thread='),
|
||||
);
|
||||
expect(link, isNotNull);
|
||||
expect(link?.threadRootId, isNull);
|
||||
});
|
||||
|
||||
test('rejects missing channel', () {
|
||||
expect(parseMessageDeepLink(Uri.parse('buzz://message?id=abc')), isNull);
|
||||
});
|
||||
|
||||
test('rejects empty channel', () {
|
||||
expect(
|
||||
parseMessageDeepLink(Uri.parse('buzz://message?channel=&id=abc')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects missing id', () {
|
||||
expect(
|
||||
parseMessageDeepLink(Uri.parse('buzz://message?channel=d14cd131')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-buzz scheme', () {
|
||||
expect(
|
||||
parseMessageDeepLink(Uri.parse('https://message?channel=a&id=b')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-message host (connect is desktop-only)', () {
|
||||
expect(
|
||||
parseMessageDeepLink(Uri.parse('buzz://connect?relay=wss://x')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _inviteTests() {
|
||||
group('parseInviteDeepLink', () {
|
||||
test('parses canonical HTTPS invite URL', () {
|
||||
final link = parseInviteDeepLink(
|
||||
Uri.parse('https://relay.example.com/invite/abc123'),
|
||||
);
|
||||
expect(
|
||||
link,
|
||||
const InviteDeepLink(
|
||||
relayUrl: 'wss://relay.example.com',
|
||||
code: 'abc123',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('parses HTTP invite URL for local/dev relays', () {
|
||||
final link = parseInviteDeepLink(
|
||||
Uri.parse('http://localhost:3000/invite/dev-code'),
|
||||
);
|
||||
expect(
|
||||
link,
|
||||
const InviteDeepLink(relayUrl: 'ws://localhost:3000', code: 'dev-code'),
|
||||
);
|
||||
});
|
||||
|
||||
test('parses buzz join handoff link', () {
|
||||
final link = parseInviteDeepLink(
|
||||
Uri.parse(
|
||||
'buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=abc123',
|
||||
),
|
||||
);
|
||||
expect(
|
||||
link,
|
||||
const InviteDeepLink(
|
||||
relayUrl: 'wss://relay.example.com',
|
||||
code: 'abc123',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizes trailing slash in buzz join handoff', () {
|
||||
final link = parseInviteDeepLink(
|
||||
Uri.parse(
|
||||
'buzz://join?relay=wss%3A%2F%2Frelay.example.com%2F&code=abc123',
|
||||
),
|
||||
);
|
||||
expect(link?.relayUrl, 'wss://relay.example.com');
|
||||
});
|
||||
|
||||
test('rejects plaintext public buzz join handoff', () {
|
||||
final relay = Uri.encodeQueryComponent('ws://relay.example.com');
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('buzz://join?relay=$relay&code=abc')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves policy receipt in buzz join handoff', () {
|
||||
final link = parseInviteDeepLink(
|
||||
Uri.parse(
|
||||
'buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=abc123&policy_receipt=receipt.value',
|
||||
),
|
||||
);
|
||||
expect(
|
||||
link,
|
||||
const InviteDeepLink(
|
||||
relayUrl: 'wss://relay.example.com',
|
||||
code: 'abc123',
|
||||
policyReceipt: 'receipt.value',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-invite HTTPS paths', () {
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('https://relay.example.com/api/invites')),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('https://relay.example.com/invite/')),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('https://relay.example.com/invite/a/b')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects credentials and fragments', () {
|
||||
expect(
|
||||
parseInviteDeepLink(
|
||||
Uri.parse('https://user:pass@relay.example.com/invite/abc'),
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(
|
||||
Uri.parse('https://relay.example.com/invite/abc#x'),
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(
|
||||
Uri.parse(
|
||||
'buzz://join?relay=wss%3A%2F%2Fuser%3Apass%40relay.example.com&code=abc',
|
||||
),
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects buzz join without websocket relay or code', () {
|
||||
expect(
|
||||
parseInviteDeepLink(
|
||||
Uri.parse('buzz://join?relay=https://relay.example.com&code=abc'),
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(
|
||||
Uri.parse('buzz://join?relay=wss://relay.example.com'),
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('buzz://connect?relay=wss://x')),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-public invite relay destinations', () {
|
||||
for (final url in [
|
||||
'https://127.0.0.1/invite/abc',
|
||||
'https://169.254.169.254/invite/abc',
|
||||
'https://192.168.1.1/invite/abc',
|
||||
'https://[::1]/invite/abc',
|
||||
'https://[::ffff:127.0.0.1]/invite/abc',
|
||||
]) {
|
||||
expect(parseInviteDeepLink(Uri.parse(url)), isNull, reason: url);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects buzz join with dangerous relay schemes', () {
|
||||
// The `relay=` param is an allowlist — only `ws` / `wss` are safe to
|
||||
// hand to a Nostr relay session. Anything else must be dropped by the
|
||||
// parser so a hostile QR / share link can't smuggle a browser scheme
|
||||
// (`javascript:`, `data:`), a local resource (`file:`), or an
|
||||
// unrelated transport (`ftp:`, `chrome:`) into the join flow.
|
||||
for (final hostile in [
|
||||
'javascript:alert(1)',
|
||||
'data:text/html,evil',
|
||||
'file:///etc/passwd',
|
||||
'ftp://relay.example.com',
|
||||
'chrome://settings',
|
||||
'about:blank',
|
||||
'ssh://relay.example.com',
|
||||
]) {
|
||||
final encoded = Uri.encodeQueryComponent(hostile);
|
||||
expect(
|
||||
parseInviteDeepLink(Uri.parse('buzz://join?relay=$encoded&code=abc')),
|
||||
isNull,
|
||||
reason: 'must reject relay scheme in $hostile',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _buildMessageLinkTests() {
|
||||
group('buildMessageLink', () {
|
||||
test('builds channel + id link', () {
|
||||
expect(
|
||||
buildMessageLink(channelId: 'd14cd131', messageId: 'abc123'),
|
||||
'buzz://message?channel=d14cd131&id=abc123',
|
||||
);
|
||||
});
|
||||
|
||||
test('includes thread root when present', () {
|
||||
expect(
|
||||
buildMessageLink(
|
||||
channelId: 'd14cd131',
|
||||
messageId: 'abc123',
|
||||
threadRootId: 'root99',
|
||||
),
|
||||
'buzz://message?channel=d14cd131&id=abc123&thread=root99',
|
||||
);
|
||||
});
|
||||
|
||||
test('treats empty thread root as absent', () {
|
||||
expect(
|
||||
buildMessageLink(
|
||||
channelId: 'd14cd131',
|
||||
messageId: 'abc123',
|
||||
threadRootId: '',
|
||||
),
|
||||
'buzz://message?channel=d14cd131&id=abc123',
|
||||
);
|
||||
});
|
||||
|
||||
test('round-trips through parseMessageDeepLink', () {
|
||||
final url = buildMessageLink(
|
||||
channelId: 'chan-1',
|
||||
messageId: 'msg-1',
|
||||
threadRootId: 'root-1',
|
||||
);
|
||||
final parsed = parseMessageDeepLink(Uri.parse(url));
|
||||
expect(
|
||||
parsed,
|
||||
const MessageDeepLink(
|
||||
channelId: 'chan-1',
|
||||
messageId: 'msg-1',
|
||||
threadRootId: 'root-1',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws on empty channel or id', () {
|
||||
expect(
|
||||
() => buildMessageLink(channelId: '', messageId: 'abc'),
|
||||
throwsArgumentError,
|
||||
);
|
||||
expect(
|
||||
() => buildMessageLink(channelId: 'chan', messageId: ''),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user