mdns_discovery_test.dart 10.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter_tools/src/base/io.dart';
6
import 'package:flutter_tools/src/base/logger.dart';
7
import 'package:flutter_tools/src/build_info.dart';
8
import 'package:flutter_tools/src/device_port_forwarder.dart';
9
import 'package:flutter_tools/src/ios/devices.dart';
10
import 'package:flutter_tools/src/mdns_discovery.dart';
11
import 'package:flutter_tools/src/project.dart';
12
import 'package:flutter_tools/src/reporting/reporting.dart';
13
import 'package:multicast_dns/multicast_dns.dart';
14
import 'package:test/fake.dart';
15 16 17 18 19 20 21

import '../src/common.dart';

void main() {
  group('mDNS Discovery', () {
    final int year3000 = DateTime(3000).millisecondsSinceEpoch;

22 23 24
    setUp(() {
      setNetworkInterfaceLister(
        ({
25 26 27
          bool? includeLoopback,
          bool? includeLinkLocal,
          InternetAddressType? type,
28 29 30 31 32 33 34 35
        }) async => <NetworkInterface>[],
      );
    });

    tearDown(() {
      resetNetworkInterfaceLister();
    });

36

37
    testWithoutContext('No ports available', () async {
38
      final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
39

40 41 42
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
43
        flutterUsage: TestUsage(),
44
      );
45
      final int? port = (await portDiscovery.query())?.port;
46 47 48
      expect(port, isNull);
    });

49
    testWithoutContext('Prints helpful message when there is no ipv4 link local address.', () async {
50
      final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{});
51 52 53 54
      final BufferLogger logger = BufferLogger.test();
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: logger,
55
        flutterUsage: TestUsage(),
56
      );
57
      final Uri? uri = await portDiscovery.getObservatoryUri(
58
        '',
59
        FakeIOSDevice(),
60 61
      );
      expect(uri, isNull);
62
      expect(logger.errorText, contains('Personal Hotspot'));
63 64
    });

65
    testWithoutContext('One port available, no appId', () async {
66
      final MDnsClient client = FakeMDnsClient(
67 68 69 70 71 72 73 74 75 76
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
        },
      );

77 78 79
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
80
        flutterUsage: TestUsage(),
81
      );
82
      final int? port = (await portDiscovery.query())?.port;
83 84 85
      expect(port, 123);
    });

86
    testWithoutContext('One port available, no appId, with authCode', () async {
87
      final MDnsClient client = FakeMDnsClient(
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
        },
        txtResponse: <String, List<TxtResourceRecord>>{
          'bar': <TxtResourceRecord>[
            TxtResourceRecord('bar', year3000, text: 'authCode=xyz\n'),
          ],
        },
      );

      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
105
        logger: BufferLogger.test(),
106
        flutterUsage: TestUsage(),
107
      );
108
      final MDnsObservatoryDiscoveryResult? result = await portDiscovery.query();
109 110 111 112
      expect(result?.port, 123);
      expect(result?.authCode, 'xyz/');
    });

113
    testWithoutContext('Multiple ports available, without appId', () async {
114
      final MDnsClient client = FakeMDnsClient(
115 116 117 118 119 120 121 122 123 124 125 126 127 128
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
          'fiz': <SrvResourceRecord>[
            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
          ],
        },
      );

129 130 131
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
132
        flutterUsage: TestUsage(),
133
      );
134
      expect(portDiscovery.query, throwsToolExit());
135 136
    });

137
    testWithoutContext('Multiple ports available, with appId', () async {
138
      final MDnsClient client = FakeMDnsClient(
139 140 141 142 143 144 145 146 147 148 149 150 151 152
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
          'fiz': <SrvResourceRecord>[
            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
          ],
        },
      );

153 154 155
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
156
        flutterUsage: TestUsage(),
157
      );
158
      final int? port = (await portDiscovery.query(applicationId: 'fiz'))?.port;
159 160 161
      expect(port, 321);
    });

162
    testWithoutContext('Multiple ports available per process, with appId', () async {
163
      final MDnsClient client = FakeMDnsClient(
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
          PtrResourceRecord('baz', year3000, domainName: 'fiz'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 1234, weight: 1, priority: 1, target: 'appId'),
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
          'fiz': <SrvResourceRecord>[
            SrvResourceRecord('fiz', year3000, port: 4321, weight: 1, priority: 1, target: 'local'),
            SrvResourceRecord('fiz', year3000, port: 321, weight: 1, priority: 1, target: 'local'),
          ],
        },
      );

180 181 182
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
183
        flutterUsage: TestUsage(),
184
      );
185
      final int? port = (await portDiscovery.query(applicationId: 'bar'))?.port;
186 187 188
      expect(port, 1234);
    });

189
    testWithoutContext('Query returns null', () async {
190
      final MDnsClient client = FakeMDnsClient(
191 192 193 194
        <PtrResourceRecord>[],
         <String, List<SrvResourceRecord>>{},
      );

195 196 197
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
198
        flutterUsage: TestUsage(),
199
      );
200
      final int? port = (await portDiscovery.query(applicationId: 'bar'))?.port;
201 202
      expect(port, isNull);
    });
203

204
    testWithoutContext('Throws Exception when client throws OSError on start', () async {
205 206
      final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}, osErrorOnStart: true);

207 208 209

      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
210
        logger: BufferLogger.test(),
211
        flutterUsage: TestUsage(),
212 213
      );
      expect(
214
        () async => portDiscovery.query(),
215
        throwsException,
216 217
      );
    });
218

219
    testWithoutContext('Correctly builds Observatory URI with hostVmservicePort == 0', () async {
220
      final MDnsClient client = FakeMDnsClient(
221 222 223 224 225 226 227 228 229 230
        <PtrResourceRecord>[
          PtrResourceRecord('foo', year3000, domainName: 'bar'),
        ],
        <String, List<SrvResourceRecord>>{
          'bar': <SrvResourceRecord>[
            SrvResourceRecord('bar', year3000, port: 123, weight: 1, priority: 1, target: 'appId'),
          ],
        },
      );

231
      final FakeIOSDevice device = FakeIOSDevice();
232 233 234
      final MDnsObservatoryDiscovery portDiscovery = MDnsObservatoryDiscovery(
        mdnsClient: client,
        logger: BufferLogger.test(),
235
        flutterUsage: TestUsage(),
236
      );
237
      final Uri? uri = await portDiscovery.getObservatoryUri('bar', device, hostVmservicePort: 0);
238 239
      expect(uri.toString(), 'http://127.0.0.1:123/');
    });
240 241 242
  });
}

243 244 245 246 247 248 249 250 251 252 253 254 255
class FakeMDnsClient extends Fake implements MDnsClient {
  FakeMDnsClient(this.ptrRecords, this.srvResponse, {
    this.txtResponse = const <String, List<TxtResourceRecord>>{},
    this.osErrorOnStart = false,
  });

  final List<PtrResourceRecord> ptrRecords;
  final Map<String, List<SrvResourceRecord>> srvResponse;
  final Map<String, List<TxtResourceRecord>> txtResponse;
  final bool osErrorOnStart;

  @override
  Future<void> start({
256 257
    InternetAddress? listenAddress,
    NetworkInterfacesFactory? interfacesFactory,
258
    int mDnsPort = 5353,
259
    InternetAddress? mDnsAddress,
260 261
  }) async {
    if (osErrorOnStart) {
262
      throw const OSError('Operation not supported on socket', 102);
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    }
  }

  @override
  Stream<T> lookup<T extends ResourceRecord>(
    ResourceRecordQuery query, {
    Duration timeout = const Duration(seconds: 5),
  }) {
    if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsObservatoryDiscovery.dartObservatoryName) {
      return Stream<PtrResourceRecord>.fromIterable(ptrRecords) as Stream<T>;
    }
    if (T == SrvResourceRecord) {
      final String key = query.fullyQualifiedName;
      return Stream<SrvResourceRecord>.fromIterable(srvResponse[key] ?? <SrvResourceRecord>[]) as Stream<T>;
    }
    if (T == TxtResourceRecord) {
      final String key = query.fullyQualifiedName;
      return Stream<TxtResourceRecord>.fromIterable(txtResponse[key] ?? <TxtResourceRecord>[]) as Stream<T>;
    }
    throw UnsupportedError('Unsupported query type $T');
  }

  @override
  void stop() {}
}

289 290 291
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
292 293 294 295 296 297 298 299 300 301 302 303 304
class FakeIOSDevice extends Fake implements IOSDevice {
  @override
  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;

  @override
  bool isSupported() => true;

  @override
  bool isSupportedForProject(FlutterProject flutterProject) => true;

  @override
  DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
}