/// <summary> /// Initializes a new instance of the <see cref="KubernetesAdbSocketLocator"/> class. /// </summary> /// <param name="kubernetes"> /// A connection to the Kubernetes cluster. /// </param> /// <param name="context"> /// The pod in which adb is running. /// </param> public KubernetesAdbSocketLocator( KubernetesClient kubernetes, KubernetesAdbContext context) { this.kubernetes = kubernetes ?? throw new ArgumentNullException(nameof(kubernetes)); this.context = context ?? throw new ArgumentNullException(nameof(context)); }
/// <summary> /// Initializes a new instance of the <see cref="KubernetesWebDriver"/> class. /// </summary> /// <param name="kubernetes"> /// A <see cref="KubernetesClient"/> which can be used to connect to Kubernetes. /// </param> /// <param name="logger"> /// A logger which can be used when logging. /// </param> public KubernetesWebDriver(KubernetesClient kubernetes, ILogger <KubernetesWebDriver> logger) { this.kubernetes = kubernetes ?? throw new ArgumentNullException(nameof(kubernetes)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.sessionClient = this.kubernetes.GetClient <WebDriverSession>(); }
public async Task WaitForCustomResourceDefinitionEstablishedAsync_CrdAlreadyEstablished_Returns_Async() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { await client.WaitForCustomResourceDefinitionEstablishedAsync( new V1CustomResourceDefinition() { Status = new V1CustomResourceDefinitionStatus() { Conditions = new V1CustomResourceDefinitionCondition[] { new V1CustomResourceDefinitionCondition() { Type = "Established", Status = "True", }, }, }, }, TimeSpan.FromMinutes(1), default).ConfigureAwait(false); } protocol.Verify(); }
public async Task AcquireLockRequest_UsesWebsiteinstanceIdIfOwnerMissing() { Mock <IEnvironment> _environment = new Mock <IEnvironment>(); _environment.Setup(p => p.GetEnvironmentVariable(HttpLeaderEndpoint)).Returns(TestHttpLeaderEndpoint); string lockId = "test-lock"; string instanceId = ScriptSettingsManager.Instance.AzureWebsiteInstanceId; var handlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict); handlerMock.Protected().Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.Is <HttpRequestMessage>(request => request.Method == HttpMethod.Post && request.RequestUri.AbsoluteUri.Equals( $"{TestHttpLeaderEndpoint}/lock/acquire?name={lockId}&owner={instanceId}&duration=5&renewDeadline=10")), ItExpr.IsAny <CancellationToken>()).ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK }); var httpClient = new HttpClient(handlerMock.Object); var lockClient = new KubernetesClient(_environment.Object, httpClient); var lockManager = new KubernetesDistributedLockManager(lockClient, ScriptSettingsManager.Instance); var lockHandle = await lockManager.TryLockAsync("", lockId, "", "", TimeSpan.FromSeconds(5), new CancellationToken()); Assert.Equal(lockId, lockHandle.LockId); Assert.Equal(instanceId, ((KubernetesLockHandle)lockHandle).Owner); }
public async Task ConnectToPodPortAsync_ValidatesArguments_Async() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { await Assert.ThrowsAsync <ArgumentNullException>(() => client.ConnectToPodPortAsync(null, 1, default).AsTask()).ConfigureAwait(false); await Assert.ThrowsAsync <ValidationException>(() => client.ConnectToPodPortAsync(new V1Pod { }, 1, default).AsTask()).ConfigureAwait(false); await Assert.ThrowsAsync <ValidationException>(() => client.ConnectToPodPortAsync(new V1Pod { Metadata = new V1ObjectMeta() }, 1, default).AsTask()).ConfigureAwait(false); await Assert.ThrowsAsync <ValidationException>(() => client.ConnectToPodPortAsync(new V1Pod { Metadata = new V1ObjectMeta() { Name = "a" } }, 1, default).AsTask()).ConfigureAwait(false); await Assert.ThrowsAsync <ValidationException>(() => client.ConnectToPodPortAsync(new V1Pod { Metadata = new V1ObjectMeta() { NamespaceProperty = "b" } }, 1, default).AsTask()).ConfigureAwait(false); } protocol.Verify(); }
[InlineData(@"{""kind"":""Status"", ""apiVersion"":""v1beta1"", ""message"":""""}")] // Empty message public async Task CreatePodAsync_InvalidKubernetesError_Async(string statusJson) { var pod = new V1Pod() { Metadata = new V1ObjectMeta() { NamespaceProperty = "default" } }; var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol .Setup(p => p.CreateNamespacedPodWithHttpMessagesAsync(pod, pod.Metadata.NamespaceProperty, null, null, null, null, default)) .ThrowsAsync( new HttpOperationException() { Response = new HttpResponseMessageWrapper( new HttpResponseMessage(HttpStatusCode.UnprocessableEntity), statusJson), }); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { await Assert.ThrowsAsync <HttpOperationException>(() => client.CreatePodAsync(pod, default)).ConfigureAwait(false); } }
public async Task CreatePodAsync_CapturesKubernetesError_Async(HttpStatusCode statusCode) { const string status = @"{""kind"":""Status"",""apiVersion"":""v1"",""metadata"":{},""status"":""Failure"",""message"":""pods 'waitforpodrunning-integrationtest-async' already exists"",""reason"":""AlreadyExists"",""details"":{ ""name"":""waitforpodrunning-integrationtest-async"",""kind"":""pods""},""code"":409}"; var pod = new V1Pod() { Metadata = new V1ObjectMeta() { NamespaceProperty = "default" } }; var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol .Setup(p => p.CreateNamespacedPodWithHttpMessagesAsync(pod, pod.Metadata.NamespaceProperty, null, null, null, null, default)) .ThrowsAsync( new HttpOperationException() { Response = new HttpResponseMessageWrapper( new HttpResponseMessage(statusCode), status), }); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { var ex = await Assert.ThrowsAsync <KubernetesException>(() => client.CreatePodAsync(pod, default)).ConfigureAwait(false); Assert.Equal("pods 'waitforpodrunning-integrationtest-async' already exists", ex.Message); Assert.NotNull(ex.Status); } }
public async Task CreateCustomResourceDefinitionAsync_UsesProtocol_Async() { var crd = new V1CustomResourceDefinition() { Metadata = new V1ObjectMeta() { Name = "my-crd" } }; var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol .Setup(p => p.CreateCustomResourceDefinitionWithHttpMessagesAsync(crd, null, null, null, null, default)) .ReturnsAsync(new HttpOperationResponse <V1CustomResourceDefinition>() { Body = crd }); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { var result = await client.CreateCustomResourceDefinitionAsync(crd, default).ConfigureAwait(false); Assert.Same(crd, result); } }
public async Task Usbmuxd_CanListDevices_Async() { var config = KubernetesClientConfiguration.BuildDefaultConfig(); if (config.Namespace == null) { config.Namespace = "default"; } using (var kubernetes = new KubernetesProtocol( config, this.loggerFactory.CreateLogger <KubernetesProtocol>(), this.loggerFactory)) using (var client = new KubernetesClient( kubernetes, KubernetesOptions.Default, this.output.BuildLoggerFor <KubernetesClient>(), this.loggerFactory)) { // There's at least one usbmuxd pod var pods = await kubernetes.ListNamespacedPodAsync(config.Namespace, labelSelector : "app.kubernetes.io/component=usbmuxd"); Assert.NotEmpty(pods.Items); var pod = pods.Items[0]; // The pod is in the running state pod = await client.WaitForPodRunningAsync(pod, TimeSpan.FromMinutes(2), default).ConfigureAwait(false); Assert.Equal("Running", pod.Status.Phase); // We can connect to port 27015 and retrieve an empty device list var locator = new KubernetesMuxerSocketLocator(kubernetes, pod, this.loggerFactory.CreateLogger <KubernetesMuxerSocketLocator>(), this.loggerFactory); var muxerClient = new MuxerClient(locator, this.loggerFactory.CreateLogger <MuxerClient>(), this.loggerFactory); var devices = await muxerClient.ListDevicesAsync(default).ConfigureAwait(false);
public void CreatePodHttpClient_ValidatesArguments() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { Assert.Throws <ArgumentNullException>("pod", () => client.CreatePodHttpClient(null, 80)); Assert.Throws <ValidationException>(() => client.CreatePodHttpClient(new V1Pod(), 80)); Assert.Throws <ValidationException>(() => client.CreatePodHttpClient(new V1Pod() { Metadata = new V1ObjectMeta() }, 80)); Assert.Throws <ValidationException>(() => client.CreatePodHttpClient(new V1Pod() { Metadata = new V1ObjectMeta() { Name = "foo" } }, 80)); Assert.Throws <ValidationException>(() => client.CreatePodHttpClient(new V1Pod() { Metadata = new V1ObjectMeta() { NamespaceProperty = "bar" } }, 80)); } }
public async Task WaitForPodRunning_PodFailed_Returns_Async() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { var ex = await Assert.ThrowsAsync <KubernetesException>(() => client.WaitForPodRunningAsync( new V1Pod() { Metadata = new V1ObjectMeta() { Name = "my-pod", }, Status = new V1PodStatus() { Phase = "Failed", Reason = "Something went wrong!", }, }, TimeSpan.FromMinutes(1), default)).ConfigureAwait(false); Assert.Equal("The pod my-pod has failed: Something went wrong!", ex.Message); } protocol.Verify(); }
public async Task ConnectToPodPortAsync_UsesPortForwarding_Async() { var pod = new V1Pod() { Metadata = new V1ObjectMeta() { NamespaceProperty = "my-namespace", Name = "usbmuxd-abcd", }, }; var websocket = Mock.Of <WebSocket>(); var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol .Setup(k => k.WebSocketNamespacedPodPortForwardAsync("usbmuxd-abcd", "my-namespace", new int[] { 27015 }, null, null, default)) .ReturnsAsync(websocket) .Verifiable(); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) using (var stream = await client.ConnectToPodPortAsync(pod, 27015, default)) { var portForwardStream = Assert.IsType <PortForwardStream>(stream); Assert.Same(websocket, portForwardStream.WebSocket); } protocol.Verify(); }
/// <summary> /// Initializes a new instance of the <see cref="KubernetesPairingRecordStore"/> class. /// </summary> /// <param name="client"> /// A <see cref="KubernetesClient"/> which can be used to connect to the Kubernetes cluster. /// </param> /// <param name="logger"> /// A logger which can be used when logging.</param> public KubernetesPairingRecordStore(KubernetesClient client, ILogger <KubernetesPairingRecordStore> logger) { this.client = client ?? throw new ArgumentNullException(nameof(client)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.secretClient = this.client.GetClient <V1Secret>(); }
public void CorrectRequestForGetOptions() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.GetOptions(); factory.Received().ExecuteRequest <KubernetesOptions>("kubernetes/options", null, null, "options"); }
public void CorrectRequestForGetAll() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.GetAll(); factory.Received().GetPaginated <KubernetesCluster>("kubernetes/clusters", null, "kubernetes_clusters"); }
public void CorrectRequestForCreate() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); var cluster = new Models.Requests.KubernetesCluster(); client.Create(cluster); factory.Received().ExecuteRequest <KubernetesCluster>("kubernetes/clusters", null, cluster, "kubernetes_cluster", Method.POST); }
public KubernetesEnvironmentOperatorTest(KubernetesClusterFixture fixture) { string deviceNamespace = $"device-{Guid.NewGuid()}"; this.client = new KubernetesClient(deviceNamespace, fixture.Client); this.runtimeInfoProvider = new KubernetesRuntimeInfoProvider(deviceNamespace, fixture.Client, new DummyModuleManager()); this.environmentOperator = new KubernetesEnvironmentOperator(deviceNamespace, this.runtimeInfoProvider, fixture.Client); }
public void CorrectRequestForGetKubeConfig() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.GetKubeConfig("1"); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1"); factory.Received().ExecuteRaw("kubernetes/clusters/{id}/kubeconfig", parameters, null, Method.GET); }
public void CorrectRequestForGetUpgrades() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.GetUpgrades("1"); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1"); factory.Received().ExecuteRequest <List <KubernetesUpgrade> >("kubernetes/clusters/{id}/upgrades", parameters, null, "available_upgrade_versions"); }
public void CorrectRequestForDeleteNodePool() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.DeleteNodePool("1", "2"); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1" && (string)list[1].Value == "2"); factory.Received().ExecuteRaw("kubernetes/clusters/{id}/node_pools/{poolId}", parameters, null, Method.DELETE); }
public void CorrectRequestForGetAllNodePools() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); client.GetAllNodePools("1"); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1"); factory.Received().GetPaginated <KubernetesNodePool>("kubernetes/clusters/{id}/node_pools", parameters, "node_pools"); }
public async Task DeletePodAsync_ApiDisconnects_Errors_Async() { var pod = new V1Pod() { Kind = V1Pod.KubeKind, Metadata = new V1ObjectMeta() { Name = "my-pod", NamespaceProperty = "default", }, Status = new V1PodStatus() { Phase = "Pending", }, }; WatchEventDelegate <V1Pod> callback = null; TaskCompletionSource <WatchExitReason> watchTask = new TaskCompletionSource <WatchExitReason>(); var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); protocol .Setup(p => p.DeleteNamespacedPodWithHttpMessagesAsync(pod.Metadata.Name, pod.Metadata.NamespaceProperty, null, null, null, null, null, null, null, default)) .Returns(Task.FromResult(new HttpOperationResponse <V1Pod>() { Body = pod, Response = new HttpResponseMessage(HttpStatusCode.OK) })).Verifiable(); protocol .Setup(p => p.WatchNamespacedObjectAsync(pod, protocol.Object.ListNamespacedPodWithHttpMessagesAsync, It.IsAny <WatchEventDelegate <V1Pod> >(), It.IsAny <CancellationToken>())) .Returns <V1Pod, ListNamespacedObjectWithHttpMessagesAsync <V1Pod, V1PodList>, WatchEventDelegate <V1Pod>, CancellationToken>((pod, list, watcher, ct) => { Assert.NotNull(list); callback = watcher; return(watchTask.Task); }); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { var task = client.DeletePodAsync(pod, TimeSpan.FromMinutes(1), default); Assert.NotNull(callback); // Simulate the watch task stopping watchTask.SetResult(WatchExitReason.ServerDisconnected); // The watch completes with an exception. var ex = await Assert.ThrowsAsync <KubernetesException>(() => task).ConfigureAwait(false); Assert.Equal("The API server unexpectedly closed the connection while watching Pod 'my-pod'.", ex.Message); } protocol.Verify(); }
public async Task CreateCustomResourceDefinitionAsync_ValidatesArguments_Async() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { await Assert.ThrowsAsync <ArgumentNullException>("value", () => client.CreateCustomResourceDefinitionAsync(null, default)).ConfigureAwait(false); } }
public void CorrectRequestForUpgrade() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); var upgrade = new Models.Requests.KubernetesUpgrade(); client.Upgrade("1", upgrade); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1"); factory.Received().ExecuteRaw("kubernetes/clusters/{id}/upgrade", parameters, upgrade, Method.POST); }
public void CorrectRequestForUpdate() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); var update = new Models.Requests.UpdateKubernetesCluster(); client.Update("1", update); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1"); factory.Received().ExecuteRequest <KubernetesCluster>("kubernetes/clusters/{id}", parameters, update, "kubernetes_cluster", Method.PUT); }
/// <summary> /// Initializes a new instance of the <see cref="UsbmuxdSidecar"/> class. /// </summary> /// <param name="muxerClient"> /// A <see cref="MuxerClient"/> which represents a connection to the local iOS USB muxer. /// </param> /// <param name="kubernetes"> /// A <see cref="KubernetesClient"/> which represents a connection to the Kubernetes cluster. /// </param> /// <param name="pairingRecordProvisioner"> /// A <see cref="PairingRecordProvisioner"/> which can be used to retrieve or generate a pairing record. /// </param> /// <param name="developerDiskProvisioner"> /// A <see cref="DeveloperDiskProvisioner"/> which can be used to mount developer disk images on the device. /// </param> /// <param name="configuration"> /// A <see cref="UsbmuxdSidecarConfiguration"/> which represents the configuration for this sidecar. /// </param> /// <param name="logger"> /// A <see cref="ILogger"/> which can be used to log messages. /// </param> public UsbmuxdSidecar(MuxerClient muxerClient, KubernetesClient kubernetes, PairingRecordProvisioner pairingRecordProvisioner, DeveloperDiskProvisioner developerDiskProvisioner, UsbmuxdSidecarConfiguration configuration, ILogger <UsbmuxdSidecar> logger) { this.muxerClient = muxerClient ?? throw new ArgumentNullException(nameof(muxerClient)); this.kubernetesClient = kubernetes ?? throw new ArgumentNullException(nameof(kubernetes)); this.pairingRecordProvisioner = pairingRecordProvisioner ?? throw new ArgumentNullException(nameof(pairingRecordProvisioner)); this.developerDiskProvisioner = developerDiskProvisioner ?? throw new ArgumentNullException(nameof(developerDiskProvisioner)); this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.deviceClient = this.kubernetesClient.GetClient <MobileDevice>(); }
public async Task AcquireLockRequestThrowsOnInvalidInput(string lockId, string ownerId) { Mock <IEnvironment> _environment = new Mock <IEnvironment>(); var handlerMock = new Mock <HttpMessageHandler>(MockBehavior.Strict); var httpClient = new HttpClient(handlerMock.Object); var lockClient = new KubernetesClient(_environment.Object, httpClient); var lockManager = new KubernetesDistributedLockManager(lockClient, ScriptSettingsManager.Instance); await Assert.ThrowsAsync <ArgumentNullException>(() => lockManager.TryLockAsync ("", lockId, ownerId, "", TimeSpan.FromSeconds(5), new CancellationToken())); }
public void CorrectRequestForUpdateNodePool() { var factory = Substitute.For <IConnection>(); var client = new KubernetesClient(factory); var pool = new Models.Requests.UpdateKubernetesNodePool(); client.UpdateNodePool("1", "2", pool); var parameters = Arg.Is <List <Parameter> >(list => (string)list[0].Value == "1" && (string)list[1].Value == "2"); factory.Received().ExecuteRequest <KubernetesNodePool>("kubernetes/clusters/{id}/node_pools/{poolId}", parameters, pool, "node_pool", Method.PUT); }
public void Dispose_DisposesProtocol() { var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance); client.Dispose(); protocol.Verify(); }
public async Task DeletePodAsync_PodDeleted_Returns_Async() { var pod = new V1Pod() { Metadata = new V1ObjectMeta() { Name = "my-pod", NamespaceProperty = "default", }, Status = new V1PodStatus() { Phase = "Pending", }, }; WatchEventDelegate <V1Pod> callback = null; TaskCompletionSource <WatchExitReason> watchTask = new TaskCompletionSource <WatchExitReason>(); var protocol = new Mock <IKubernetesProtocol>(MockBehavior.Strict); protocol.Setup(p => p.Dispose()).Verifiable(); protocol .Setup(p => p.DeleteNamespacedPodWithHttpMessagesAsync(pod.Metadata.Name, pod.Metadata.NamespaceProperty, null, null, null, null, null, null, null, default)) .Returns(Task.FromResult(new HttpOperationResponse <V1Pod>() { Body = pod, Response = new HttpResponseMessage(HttpStatusCode.OK) })).Verifiable(); protocol .Setup(p => p.WatchNamespacedObjectAsync(pod, protocol.Object.ListNamespacedPodWithHttpMessagesAsync, It.IsAny <WatchEventDelegate <V1Pod> >(), It.IsAny <CancellationToken>())) .Returns <V1Pod, ListNamespacedObjectWithHttpMessagesAsync <V1Pod, V1PodList>, WatchEventDelegate <V1Pod>, CancellationToken>((pod, list, watcher, ct) => { Assert.NotNull(list); callback = watcher; return(watchTask.Task); }); using (var client = new KubernetesClient(protocol.Object, KubernetesOptions.Default, NullLogger <KubernetesClient> .Instance, NullLoggerFactory.Instance)) { var task = client.DeletePodAsync(pod, TimeSpan.FromMinutes(1), default); Assert.NotNull(callback); // The callback continues watching until the pod is deleted Assert.Equal(WatchResult.Continue, await callback(WatchEventType.Modified, pod).ConfigureAwait(false)); Assert.Equal(WatchResult.Stop, await callback(WatchEventType.Deleted, pod).ConfigureAwait(false)); watchTask.SetResult(WatchExitReason.ClientDisconnected); await task.ConfigureAwait(false); } protocol.Verify(); }