Search code examples
c#unit-testingpostopenrasta

OpenRasta Unit Test Response HTTP Error 415


I have inherited a project from my predecessor which uses OpenRasta to host a webservice for my OpenSource colleagues to access for their applications. This is my first foray into OpenRasta I've added a lot of additional features all of which is working via manual browser requests, although not 100% reliably but that's perhaps another question later. So I have embarked on creating a set of Unit Tests to test the functionality, which I should be doing anyway. I have successfully created a unit test or two for each GET request all of which are passing, but I am stuck on the test for the single POST I have in the project.

I'm getting a HTTP 415 Error '8-[2012-12-07 11:23:19Z] Information(0) Executing OperationResult OperationResult: type=RequestM ediaTypeUnsupported, statusCode=415.' from the output window. I've taken inspiration from a post by Nate Taylor http://taylonr.com/integration-testing-openrasta and have asked him the same question, which he has kindly replied to. I'm still trying to decipher his answer, and perhaps someone might be able to expand and fill in the gaps in my understanding? Here is the code which I have been trying:

[Test]
public void AuthenticateUserJSONPOSTTest()
    {
        object content = new AuthenticationStructure { Username = "matthew.radford", Password = "obviously not going to tell you that bit and will replace with a domain test user when the time comes", AppId = 4 };

        OpenRastaJSONTestMehods.POST<AuthenticationResult, AuthenticationStructure>("http://localhost/user", content);
    }

[Test]
    public static void POST<T, U>(string uri, object content)
    {
        const string LocalHost = "http://localhost/";
        if (uri.Contains(LocalHost))
            POST<T, U>(new Uri(uri), content);
        else
            throw new UriFormatException(string.Format("The uri doesn't contain {0}", LocalHost));
    }
    [Test, WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    public static void POST<T,U>(Uri serviceuri, object content)
    {
        using (var host = new InMemoryHost(new Configuration()))
        {
            var request = new InMemoryRequest()
            {
                Uri = serviceuri,
                HttpMethod = "POST"
            };

            request.Entity.ContentType = MediaType.Json;
            request.Entity.Headers["Accept"] = "application/json";

            var serializer = new DataContractJsonSerializer(typeof(T), new [] { typeof(AuthenticationStructure) });
            serializer.WriteObject(request.Entity.Stream, content); 
            request.Entity.Stream.Seek(0, SeekOrigin.Begin); 
            request.Entity.ContentLength = request.Entity.Stream.Length;

            //Just a read test, not necessary for the output
            byte[] readbyte = new byte[(int)request.Entity.ContentLength];
            request.Entity.Stream.Read(readbyte, 0, (int)request.Entity.ContentLength);
            request.Entity.Stream.Seek(0, SeekOrigin.Begin);

            U readObject = (U)serializer.ReadObject(request.Entity.Stream);
            request.Entity.Stream.Seek(0, SeekOrigin.Begin);

            NUnit.Framework.Assert.AreEqual(content, readObject);

            var response = new InMemoryResponse();

            response.Entity.ContentType = MediaType.Json;
            response.Entity.Headers["Accept"] = "application/json";

            response = (InMemoryResponse)host.ProcessRequest(request);
            int statusCode = response.StatusCode;

//this is where the test fails because the above response isn't correct and gives the 415 statusCode
            NUnit.Framework.Assert.AreEqual(201, statusCode, string.Format("Http StatusCode Error: {0}", statusCode));

            object returnedObject;
            if (response.Entity.ContentLength > 0)
            {
                response.Entity.Stream.Seek(0, SeekOrigin.Begin);

                //Just a read test, not necessary for the output
                readbyte = new byte[(int)response.Entity.ContentLength];
                response.Entity.Stream.Read(readbyte, 0, (int)response.Entity.ContentLength);
                response.Entity.Stream.Seek(0, SeekOrigin.Begin);

                returnedObject = serializer.ReadObject(response.Entity.Stream);
                //return returnedObject;
            }
        }
    }

Thanks in advance.


Solution

  • I've tried so many different things this morning to try and get this working. The first good step forward I made by trying to read the JSON stream as a string to actually see what the object was being serialized as.

    To do that I found How to convert an Stream into a byte[] in C#? this set me off in the right direction to read the stream out to a string. I therefore came up with this line to write it to the output window:

    Debug.WriteLine(Encoding.UTF8.GetString(StreamHelper.ReadToEnd(request.Entity.Stream), 0, (int)request.Entity.Stream.Length).ToString());
    

    This was the result: {"__type":"AuthenticationStructure","username":"matthew.radford","appid":4,"password":"###########"}

    I realised there were two problems in this output. Firstly and simply, the password should be before the appid, which was easily fixed in the AuthenticationStructure class, where I had made a mistake. DataMember Order needed to equal 3 for AppId

    [DataMember(Name="appid", Order=3)]
        public int AppId { get; set; }
    

    Secondly though, the default serialization includes a '__type' member at the beginning of the notation. This obviously then doesn't match my parameters on the POST method of my Handler:

    [HttpOperation(HttpMethod.POST)] 
        public OperationResult Post(string username, string password, int appid)
    

    At this point I looked at trying to remove the type notation from the JSON string. I found a good sites Deserialize array values to .NET properties using DataContractJsonSerializer which showed me both how to write the constructor to include alwaysEmitTypeInformation which they had set to false, but I wanted to Emit the type information, so changed it to true. And it also showed me how to create an Surrogate based on IDataContractSurrogate, which I called AuthenticationTypeSurrogate.

    public class AuthenticationTypeSurrogate : IDataContractSurrogate
    {
        public Type GetDataContractType(Type type)
        {
            // "Book" will be serialized as an object array
            // This method is called during serialization, deserialization, and schema export. 
            if (typeof(AuthenticationStructure).IsAssignableFrom(type))
            {
                return typeof(object[]);
            }
            return type;
        }
        public object GetObjectToSerialize(object obj, Type targetType)
        {
            // This method is called on serialization.
            if (obj is AuthenticationStructure)
            {
                AuthenticationStructure authenticationStructure = (AuthenticationStructure)obj;
                return new object[] { authenticationStructure.Username, authenticationStructure.Password, authenticationStructure.AppId };
            }
            return obj;
        }
        public object GetDeserializedObject(object obj, Type targetType)
        {
            // This method is called on deserialization.
            if (obj is object[])
            {
                object[] arr = (object[])obj;
                AuthenticationStructure authenticationStructure = new AuthenticationStructure { Username = (string)arr[0], Password = (string)arr[1], AppId = (int)arr[2] };
                return authenticationStructure;
            }
            return obj;
        }
        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            return null; // not used
        }
        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            return typeDeclaration; // Not used
        }
        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            return null; // not used
        }
        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            return null; // not used
        }
        public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
        {
            return; // not used
        }
    }
    

    The serialization worked for this but now the Deserialization, which I never bothered to debug and get right because it still wasn't working because instead of creating a JSON it was serializing a object array, which you would expected because that was what the method GetObjectoSerialize method is returning. So I hit another brick wall unless I could work out how to change this into JSON.

    So finally I thought well I'll just add the __type parameter into an overloaded POST method and pass on the other parameters to the original POST method.

    [HttpOperation(HttpMethod.POST)]
        public OperationResult Post(Type __type, string username, string password, int appid)
        {
            return Post(username, password, appid);
        }
    

    This was so nearly right but it still didn't work, so finally I created another overloaded method and passed the whole type:

    [HttpOperation(HttpMethod.POST)]
        public OperationResult Post(AuthenticationStructure astruct)
        {
            return Post(astruct.Username, astruct.Password, astruct.AppId);
        }
    

    And finally this worked. I'm not perfectly happy with it and would have liked to link straight into my existing method, but it works.

    Thanks to everyone who looked and especially Nate, thanks for your original responses, and hopefully this will help people in the future.