public void ClientMessageSent_PropertyCheck() { var client = new Mock <ISubscriptionClientProxy>(); var result = new Mock <IGraphOperationResult>(); client.Setup(x => x.Id).Returns("client1"); var message = new ApolloServerDataMessage("123", result.Object); var entry = new ApolloClientMessageSentLogEntry(client.Object, message); Assert.AreEqual("client1", entry.ClientId); Assert.AreEqual(message.Type.ToString(), entry.MessageType); Assert.AreEqual(message.Id, entry.MessageId); Assert.AreNotEqual(entry.ToString(), entry.GetType().Name); }
public async Task DataMessage_WithData_SerializesCorrectly() { var server = new TestServerBuilder() .AddGraphController <ApolloDataMessageController>() .AddSubscriptionServer() .Build(); var client = server.CreateSubscriptionClient(); var context = server.CreateQueryContextBuilder() .AddQueryText("query { getValue { property1 property2 } }") .Build(); await server.ExecuteQuery(context); var message = new ApolloServerDataMessage("abc111", context.Result); var converter = new ApolloServerDataMessageConverter( server.Schema, server.ServiceProvider.GetRequiredService <IGraphResponseWriter <GraphSchema> >()); var options = new JsonSerializerOptions(); options.Converters.Add(converter); var response = JsonSerializer.Serialize(message, message.GetType(), options); var expected = @" { ""type"" : ""data"", ""id"" : ""abc111"", ""payload"": { ""data"": { ""getValue"" : { ""property1"": ""abc123"", ""property2"" : 15 } } } }"; CommonAssertions.AreEqualJsonStrings(expected, response); }
/// <summary> /// Instructs the client to process the new event. If this is an event the client subscribes /// to it should process the data appropriately and send down any data to its underlying connection /// as necessary. /// </summary> /// <param name="field">The unique field corrisponding to the event that was raised /// by the publisher.</param> /// <param name="sourceData">The source data sent from the publisher when the event was raised.</param> /// <param name="cancelToken">A cancellation token.</param> /// <returns>Task.</returns> public async Task ReceiveEvent(GraphFieldPath field, object sourceData, CancellationToken cancelToken = default) { await Task.Yield(); if (field == null) { return; } var subscriptions = _subscriptions.RetreiveByRoute(field); _logger?.SubscriptionEventReceived(field, subscriptions); if (subscriptions.Count == 0) { return; } var runtime = this.ServiceProvider.GetRequiredService <IGraphQLRuntime <TSchema> >(); var schema = this.ServiceProvider.GetRequiredService <TSchema>(); var tasks = new List <Task>(); foreach (var subscription in subscriptions) { IGraphQueryExecutionMetrics metricsPackage = null; IGraphEventLogger logger = this.ServiceProvider.GetService <IGraphEventLogger>(); if (schema.Configuration.ExecutionOptions.EnableMetrics) { var factory = this.ServiceProvider.GetRequiredService <IGraphQueryExecutionMetricsFactory <TSchema> >(); metricsPackage = factory.CreateMetricsPackage(); } var context = new GraphQueryExecutionContext( runtime.CreateRequest(subscription.QueryData), this.ServiceProvider, this.User, metricsPackage, logger); // register the event data as a source input for the target subscription field context.DefaultFieldSources.AddSource(subscription.Field, sourceData); context.QueryPlan = subscription.QueryPlan; context.QueryOperation = subscription.QueryOperation; tasks.Add(runtime.ExecuteRequest(context, cancelToken) .ContinueWith( task => { if (task.IsFaulted) { return(task); } // send the message with the resultant data package var message = new ApolloServerDataMessage(subscription.Id, task.Result); return(this.SendMessage(message)); }, cancelToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); }
/// <summary> /// Parses the message contents to generate a valid client subscription and adds it to the watched /// set for this instance. /// </summary> /// <param name="message">The message with the subscription details.</param> private async Task ExecuteStartRequest(ApolloClientStartMessage message) { // ensure the id isnt already in use if (!_reservedMessageIds.ReserveMessageId(message.Id)) { await this .SendMessage(new ApolloServerErrorMessage( $"The message id {message.Id} is already reserved for an outstanding request and cannot " + "be processed against. Allow the in-progress request to complete or stop the associated subscription.", SubscriptionConstants.ErrorCodes.DUPLICATE_MESSAGE_ID, lastMessage : message, clientProvidedId : message.Id)) .ConfigureAwait(false); return; } var retainMessageId = false; var runtime = this.ServiceProvider.GetRequiredService(typeof(IGraphQLRuntime <TSchema>)) as IGraphQLRuntime <TSchema>; var request = runtime.CreateRequest(message.Payload); var metricsPackage = _enableMetrics ? runtime.CreateMetricsPackage() : null; var context = new SubcriptionExecutionContext( this, request, message.Id, metricsPackage); var result = await runtime .ExecuteRequest(context) .ConfigureAwait(false); if (context.IsSubscriptionOperation) { retainMessageId = await this .RegisterSubscriptionOrRespond(context.Subscription as ISubscription <TSchema>) .ConfigureAwait(false); } else { // not a subscription, just send back the generated response and close out the id ApolloMessage responseMessage; // report syntax errors as error messages // allow others to bubble into a fully reslt (per apollo spec) if (result.Messages.Count == 1 && result.Messages[0].Code == Constants.ErrorCodes.SYNTAX_ERROR) { responseMessage = new ApolloServerErrorMessage( result.Messages[0], message, message.Id); } else { responseMessage = new ApolloServerDataMessage(message.Id, result); } await this .SendMessage(responseMessage) .ConfigureAwait(false); await this .SendMessage(new ApolloServerCompleteMessage(message.Id)) .ConfigureAwait(false); } if (!retainMessageId) { _reservedMessageIds.ReleaseMessageId(message.Id); } }