예제 #1
0
        private static void TestSupportedLinqQueries(Func <DataServiceContext, LinqTestCase[]> getTests)
        {
            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.StartService();

                DataServiceContext context = new DataServiceContext(request.ServiceRoot, ODataProtocolVersion.V4);
                //context.EnableAtom = true;
                context.MergeOption = MergeOption.NoTracking;

                var tests = getTests(context);

                for (int i = 0; i < tests.Length; i++)
                {
                    playbackService.OverridingPlayback = tests[i].ServerPayload;
                    VerifyURI(context, tests[i].Query, tests[i].ExpectedUri, tests[i].ExpectedResults, tests[i].ExpectKeyInUri);
                }
            }
        }
예제 #2
0
            [Ignore] // Remove Atom
            // [TestCategory("Partition2"), TestMethod, Variation]
            public void BatchContentTypeTest()
            {
                var testCases = new BatchContentTypeTestCase[]
                {
                    // Completely wrong content type
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "text/plain",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = DataServicesClientResourceUtil.GetString("Batch_ExpectedContentType", "text/plain")
                    },
                    // Just type is correct, subtype is wrong
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/text",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = DataServicesClientResourceUtil.GetString("Batch_ExpectedContentType", "multipart/text")
                    },
                    // No boundary - still wrong
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/mixed",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = ODataLibResourceUtil.GetString("MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads", "multipart/mixed", "boundary")
                    },
                    // Some other parameter but no boundary
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/mixed;param=value",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = ODataLibResourceUtil.GetString("MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads", "multipart/mixed;param=value", "boundary")
                    },
                    // Empty boundary - fails
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/mixed;boundary=",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = ODataLibResourceUtil.GetString("ValidationUtils_InvalidBatchBoundaryDelimiterLength", string.Empty, "70")
                    },
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/mixed;boundary=;param=value",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = ODataLibResourceUtil.GetString("ValidationUtils_InvalidBatchBoundaryDelimiterLength", string.Empty, "70")
                    },
                    // Two boundary parameters - wrong
                    new BatchContentTypeTestCase
                    {
                        ContentType                = "multipart/mixed;boundary=one;boundary=two",
                        ExpectedErrorStatusCode    = 400,
                        ExpectedClientErrorMessage = ODataLibResourceUtil.GetString("MediaTypeUtils_BoundaryMustBeSpecifiedForBatchPayloads", "multipart/mixed;boundary=one;boundary=two", "boundary")
                    },
                    // Valid simple boundary
                    new BatchContentTypeTestCase
                    {
                        ContentType          = "multipart/mixed;boundary=batchboundary",
                        PayloadBatchBoundary = "batchboundary"
                    },
                    // Valid simple boundary - mimetype using different casing
                    new BatchContentTypeTestCase
                    {
                        ContentType          = "MultiPart/mIxed;boundary=batchboundary",
                        PayloadBatchBoundary = "batchboundary"
                    },
                    // Valid simple boundary - boundary parameter name different casing
                    new BatchContentTypeTestCase
                    {
                        ContentType          = "multipart/mixed;BounDary=batchboundary",
                        PayloadBatchBoundary = "batchboundary"
                    },
                };

                OpenWebDataServiceDefinition serverService = new OpenWebDataServiceDefinition()
                {
                    DataServiceType = typeof(CustomDataContext)
                };

                PlaybackServiceDefinition clientService = new PlaybackServiceDefinition();

                TestUtil.RunCombinations(
                    testCases,
                    (testCase) =>
                {
                    using (TestWebRequest request = serverService.CreateForInProcess())
                    {
                        request.RequestContentType = testCase.ContentType;
                        request.SetRequestStreamAsText(string.Format(
                                                           "--{0}\r\n" +
                                                           "Content-Type: multipart/mixed; boundary=changesetresponse_00000001-0000-0000-0000-000000000000\r\n\r\n" +
                                                           "--changesetresponse_00000001-0000-0000-0000-000000000000\r\n" +
                                                           "--changesetresponse_00000001-0000-0000-0000-000000000000--\r\n" +
                                                           "--{0}--\r\n", testCase.PayloadBatchBoundary));
                        request.RequestUriString = "/$batch";
                        request.HttpMethod       = "POST";

                        Exception exception = TestUtil.RunCatching(request.SendRequest);

                        int actualStatusCode = 0;
                        if (exception != null)
                        {
                            actualStatusCode = request.ResponseStatusCode;
                        }
                        else
                        {
                            Assert.AreEqual(202, request.ResponseStatusCode, "Wrong response code for no-exception request.");
                            BatchWebRequest batchResponse = BatchWebRequest.FromResponse(InMemoryWebRequest.FromResponse(request));
                            if (batchResponse.Parts.Count > 0)
                            {
                                actualStatusCode = batchResponse.Parts[0].ResponseStatusCode;
                                if (actualStatusCode == 200)
                                {
                                    actualStatusCode = 0;
                                }
                            }
                        }

                        Assert.AreEqual(testCase.ExpectedErrorStatusCode, actualStatusCode, "Wrong status code.");
                    }

                    using (TestWebRequest request = clientService.CreateForInProcessWcf())
                    {
                        request.StartService();

                        clientService.ProcessRequestOverride = clientRequest =>
                        {
                            var clientResponse = new InMemoryWebRequest();
                            clientResponse.SetResponseStatusCode(202);
                            clientResponse.ResponseHeaders["Content-Type"] = testCase.ContentType;
                            clientResponse.SetResponseStreamAsText(string.Format(
                                                                       "--{0}\r\n" +
                                                                       "Content-Type: application/http\r\n" +
                                                                       "Content-Transfer-Encoding: binary\r\n" +
                                                                       "\r\n" +
                                                                       "200 OK\r\n" +
                                                                       "<feed xmlns='http://www.w3.org/2005/Atom'/>\r\n" +
                                                                       "--{0}--\r\n",
                                                                       testCase.PayloadBatchBoundary));
                            return(clientResponse);
                        };

                        DataServiceContext ctx = new DataServiceContext(request.ServiceRoot);
                        Exception exception    = TestUtil.RunCatching(() => ctx.ExecuteBatch(ctx.CreateQuery <Customer>("/Customers")));

                        if (exception != null)
                        {
                            exception = ((DataServiceRequestException)exception).InnerException;
                            Assert.AreEqual(testCase.ExpectedClientErrorMessage, exception.Message, "Unexpected error message.");
                        }
                        else
                        {
                            Assert.IsNull(testCase.ExpectedClientErrorMessage, "Expected exception, but none was thrown.");
                        }
                    }
                });
            }
예제 #3
0
        public void PrimitiveTypes_Generated_WithIncorrectNullability()
        {
            string orderPayloadWithNullableProperties = @"HTTP/1.1 200 OK
Proxy-Connection: Keep-Alive
Connection: Keep-Alive
Content-Length: 2542
Via: 1.1 TK5-PRXY-08
Expires: Wed, 04 Jan 2012 23:39:02 GMT
Date: Wed, 04 Jan 2012 23:38:02 GMT
Content-Type: application/atom+xml;charset=utf-8
Server: Microsoft-IIS/7.5
Cache-Control: private
Vary: *
OData-Version: 4.0;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<feed xml:base='http://services.odata.org/Northwind/Northwind.svc/' xmlns:d='http://docs.oasis-open.org/odata/ns/data' xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>OrderDetails</title>
  <id>http://services.odata.org/Northwind/Northwind.svc/OrderDetails</id>
  <updated>2012-01-04T23:32:26Z</updated>
  <link rel='self' title='OrderDetails' href='OrderDetails' />
  <entry>
    <id>http://services.odata.org/Northwind/Northwind.svc/OrderDetails(10248)</id>
    <title type='text'></title>
    <updated>2012-01-04T23:32:26Z</updated>
    <author>
      <name />
    </author>
    <link rel='edit' title='OrderDetail' href='OrderDetails(10248)' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/Customer' type='application/atom+xml;type=entry' title='Customer' href='OrderDetails(10248)/Customer' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/Employee' type='application/atom+xml;type=entry' title='Employee' href='OrderDetails(10248)/Employee' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/OrderDetail_Details' type='application/atom+xml;type=feed' title='OrderDetail_Details' href='OrderDetails(10248)/OrderDetail_Details' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/Shipper' type='application/atom+xml;type=entry' title='Shipper' href='OrderDetails(10248)/Shipper' />
    <category term='NorthwindModel.OrderDetail' scheme='http://docs.oasis-open.org/odata/ns/scheme' />
    <content type='application/xml'>
      <m:properties>
        <d:OrderID m:type='Edm.Int32'>10248</d:OrderID>
        <d:ProductID m:type='Edm.Int32'>10248</d:ProductID>
        <d:UnitPrice m:null='true'/>
        <d:Quantity m:null='true'/> 
      </m:properties>
    </content>
  </entry>
</feed>";


            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();
                DataServiceContext ctx = new DataServiceContext(request.ServiceRoot);
                ctx.EnableAtom = true;
                playbackService.OverridingPlayback = orderPayloadWithNullableProperties;
                InvalidOperationException nullAssignmentError = null;
                try
                {
                    foreach (var orderDetail in ctx.CreateQuery <OrderDetail>("OrderDetails"))
                    {
                    }
                }
                catch (InvalidOperationException ioException)
                {
                    nullAssignmentError = ioException;
                }
                finally
                {
                    request.StopService();
                }

                Assert.IsNotNull(nullAssignmentError, "Client library should not allow assignment of null values to non-nullable properties");
                Assert.AreEqual(ODataLibResourceUtil.GetString("ReaderValidationUtils_NullNamedValueForNonNullableType", "UnitPrice", "Edm.Double"), nullAssignmentError.Message);
            }
        }
예제 #4
0
        public void OperationDescriptorsShouldBeUpdatedForEachRequest()
        {
            #region Response payload
            var response = @"HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: 2011
Date: Thu, 06 Jun 2013 22:07:07 GMT
Content-Type: application/atom+xml;type=entry;charset=utf-8
Server: Microsoft-IIS/7.5
Cache-Control: no-cache
X-Content-Type-Options: nosniff
OData-Version: 4.0;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

<?xml version='1.0' encoding='utf-8'?>
<entry xml:base='http://services.odata.org/V3/(S(imdxx1ujw4ludze1t3k2wmgs))/OData/OData.svc/' xmlns='http://www.w3.org/2005/Atom' xmlns:d='http://docs.oasis-open.org/odata/ns/data' xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' xmlns:georss='http://www.georss.org/georss' xmlns:gml='http://www.opengis.net/gml'>
    <id>http://services.odata.org/V3/(S(imdxx1ujw4ludze1t3k2wmgs))/OData/OData.svc/Products(0)</id>
    <category term='ODataDemo.Product' scheme='http://docs.oasis-open.org/odata/ns/scheme' />
    <link rel='edit' title='Product' href='Products(0)' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/Category' type='application/atom+xml;type=entry' title='Category' href='Products(0)/Category' />
    <link rel='http://docs.oasis-open.org/odata/ns/related/Supplier' type='application/atom+xml;type=entry' title='Supplier' href='Products(0)/Supplier' />
    <title type='text'>Bread</title>
    <summary type='text'>Whole grain bread</summary>
    <updated>2013-06-06T22:07:08Z</updated>
    <author>
        <name />
    </author>
    <link rel='http://docs.oasis-open.org/odata/ns/relatedlinks/OrderDetails' type='application/xml' title='OrderDetails' href='Products(0)/OrderDetails/$ref' />
    <m:action metadata='http://services.odata.org/V3/(S(imdxx1ujw4ludze1t3k2wmgs))/OData/OData.svc/$metadata#DemoService.{0}' title='{0}' target='http://services.odata.org/V3/(S(imdxx1ujw4ludze1t3k2wmgs))/OData/OData.svc/Products(0)/{0}' />
    <content type='application/xml'>
        <m:properties>
            <d:ID m:type='Edm.Int32'>0</d:ID>
            <d:ProductName m:type='Edm.String'>Demo Product</d:ProductName>
            <d:Discontinued m:type='Edm.Boolean'>false</d:Discontinued>
        </m:properties>
    </content>
</entry>
";

            #endregion

            // Consider two actions that an entity Product supports, and they are mutually exclusive, i.e. only one action
            // is returned in the response
            string action1 = "Discount";
            string action2 = "Discount1234";

            // Create two responses with each of above actions
            var response1 = string.Format(response, action1);
            var response2 = string.Format(response, action2);

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();
            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();
                DataServiceContext ctx = new DataServiceContext(request.ServiceRoot, ODataProtocolVersion.V4);
                ctx.EnableAtom = true;

                try
                {
                    for (int i = 0; i < 2; i++)
                    {
                        playbackService.OverridingPlayback = (i % 2 == 0) ? response1 : response2;

                        Product product = ctx.Execute <Product>(new Uri("Products(0)", UriKind.Relative)).First();

                        EntityDescriptor descriptor = ctx.GetEntityDescriptor(product);

                        descriptor.OperationDescriptors.Count.Should().Be(1);

                        var actionName = descriptor.OperationDescriptors.First().Title;

                        if (i % 2 == 0)
                        {
                            actionName.Should().Be(action1);
                        }
                        else
                        {
                            actionName.Should().Be(action2);
                        }
                    }
                }
                finally
                {
                    request.StopService();
                }
            }
        }
예제 #5
0
        public void AtomParserAttributeNamespacePrecedenceTest()
        {
            const string atomPayload =
                @"HTTP/1.1 200 OK
Content-Type: application/atom+xml

<feed xmlns:d='http://docs.oasis-open.org/odata/ns/data' 
      xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' 
      xmlns:atom='http://www.w3.org/2005/Atom' 
      xmlns:invalid='http://odata.org/invalid/namespace' 
      xmlns:gml='http://www.opengis.net/gml'
      xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>ASet</title>
  <id>http://host/ASet</id>
  <updated>2010-09-01T23:36:00Z</updated>
  <link rel='self' title='ASet' href='ASet' />
  <entry>
    <id>http://host/ASet(1)</id>
    <title type='text'></title>
    <updated>2010-09-01T23:36:00Z</updated>
    <author>
      <name />
    </author>
    <link rel='self' title='EntityA' href='ASet(1)' />
    <link rel='edit' title='EntityA' href='ASet(1)' />
    <link rel='edit-media' title='EntityA' href='ASet(1)/$value' />
    <category term='NamespaceName.Invalid' scheme='http://docs.oasis-open.org/odata/ns/data/unsupported' 
              atom:term='NamespaceName.EntityA' atom:scheme='http://docs.oasis-open.org/odata/ns/scheme'/>
    <content type='application/invalid' atom:type='application/xml' 
             src='http://odata.org/invalid' atom:src='http://odata.org/readstream1'/>
    <m:properties>
      <d:ID type='Edm.Invalid' m:type='Edm:Int32'>1</d:ID>
    </m:properties>
  </entry>
</feed>";

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();

                DataServiceContext ctx = new DataServiceContext(request.ServiceRoot, ODataProtocolVersion.V4);
                ctx.EnableAtom = true;
                playbackService.OverridingPlayback = atomPayload;


                EntityA[] items;
                try
                {
                    items = ctx.CreateQuery <EntityA>("ASet").ToArray();
                }
                catch (InvalidOperationException ex)
                {
                    Assert.Fail("Exception received but did not expect any failure.\n" + ex.Message);
                    return;
                }

                Assert.AreEqual(1, items.Length, "Expected 1 item in the payload.");
                var firstItem = items[0];

                var entities = ctx.Entities;
                Assert.AreEqual(1, entities.Count, "Expected 1 entity in the context.");
                var firstEntity = entities[0];

                // Verify that the atom:scheme and atom:term attribute are ignored
                // Verify that since the scheme attribute value is wrong, the term attribute value is not read
                Assert.AreEqual(null, firstEntity.ServerTypeName, "Type names don't match.");

                // Verify content/@src by checking that the read stream of the entry is correct
                Assert.AreEqual("http://odata.org/invalid", firstEntity.ReadStreamUri.OriginalString, "ReadStreams don't match.");

                // Verify content/@type by making sure the properties were read
                Assert.AreEqual(1, firstItem.ID, "IDs don't match.");
            }
        }
예제 #6
0
        public void AtomParserAttributeNamespaceTest()
        {
            const string atomPayloadTemplate =
                @"HTTP/1.1 200 OK
Content-Type: application/atom+xml

<feed xmlns:d='http://docs.oasis-open.org/odata/ns/data' 
      xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' 
      xmlns:atom='http://www.w3.org/2005/Atom' 
      xmlns:invalid='http://odata.org/invalid/namespace' 
      xmlns:gml='http://www.opengis.net/gml'
      xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>ASet</title>
  <id>http://host/ASet</id>
  <updated>2010-09-01T23:36:00Z</updated>
  <link rel='self' title='ASet' href='ASet' />
  <entry>
    <id>http://host/ASet(1)</id>
    <title type='text'></title>
    <updated>2010-09-01T23:36:00Z</updated>
    <author>
      <name />
    </author>
    <link rel='self' title='EntityA' href='ASet(1)' />
    <link rel='edit' title='EntityA' href='ASet(1)' />
    <link rel='edit-media' title='EntityA' href='ASet(1)/$value' />
    <category {0}='NamespaceName.EntityA' {1}='http://docs.oasis-open.org/odata/ns/scheme' />
    <content {3}='http://odata.org/readstream1' />
    <m:properties>
      <d:ID {4}='Edm.Int32'>1</d:ID>
    </m:properties>
  </entry>
</feed>";

            const string defaultCategoryTerm   = "term";
            const string defaultCategoryScheme = "scheme";
            const string defaultContentType    = "type";
            const string defaultContentSource  = "src";
            const string defaultPropertyType   = "m:type";

            AtomParserAttributeNamespaceTestCase[] testCases = new AtomParserAttributeNamespaceTestCase[]
            {
                // Error cases
                new AtomParserAttributeNamespaceTestCase {
                    CategoryScheme = "invalid:scheme"
                },
                new AtomParserAttributeNamespaceTestCase {
                    CategoryTerm = "invalid:term"
                },
                new AtomParserAttributeNamespaceTestCase {
                    ContentType = "invalid:type"
                },
                new AtomParserAttributeNamespaceTestCase {
                    ContentSrc = "invalid:src"
                },
                new AtomParserAttributeNamespaceTestCase {
                    PropertyType = "invalid:type"
                },
            };

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();

                TestUtil.RunCombinations(
                    testCases,
                    testCase =>
                {
                    DataServiceContext ctx = new DataServiceContext(request.ServiceRoot, ODataProtocolVersion.V4);
                    ctx.EnableAtom         = true;

                    // {0} - atom:category/@term
                    // {1} - atom:category/@scheme
                    // {2} - atom:content/@type
                    // {3} - atom:content/@src
                    // {4} - d:property/@m:type
                    string atomPayload = string.Format(
                        atomPayloadTemplate,
                        testCase.CategoryTerm ?? defaultCategoryTerm,
                        testCase.CategoryScheme ?? defaultCategoryScheme,
                        testCase.ContentType ?? defaultContentType,
                        testCase.ContentSrc ?? defaultContentSource,
                        testCase.PropertyType ?? defaultPropertyType);
                    playbackService.OverridingPlayback = atomPayload;


                    EntityA[] items;
                    try
                    {
                        items = ctx.CreateQuery <EntityA>("ASet").ToArray();
                    }
                    catch (InvalidOperationException ex)
                    {
                        if (testCase.ContentSrc != null && testCase.ContentSrc.StartsWith("invalid"))
                        {
                            Assert.AreEqual(ODataLibResourceUtil.GetString("ODataAtomReader_MediaLinkEntryMismatch"),
                                            ex.Message);
                            return;
                        }
                        else if (testCase.PropertyType != null && testCase.PropertyType.StartsWith("invalid"))
                        {
                            Assert.AreEqual(DataServicesClientResourceUtil.GetString("Deserialize_ExpectingSimpleValue"), ex.Message);
                            return;
                        }

                        Assert.Fail("Exception received but did not expect any failure.\n" + ex.Message);
                        return;
                    }

                    Assert.AreEqual(1, items.Length, "Expected 1 item in the payload.");
                    var firstItem = items[0];

                    var entities = ctx.Entities;
                    Assert.AreEqual(1, entities.Count, "Expected 1 entity in the context.");
                    var firstEntity = entities[0];

                    // Verify category/@term and category/@scheme by testing the type name
                    string expectedTypeName =
                        testCase.CategoryScheme != null && testCase.CategoryScheme.StartsWith("invalid") ||
                        testCase.CategoryTerm != null && testCase.CategoryTerm.StartsWith("invalid")
                            ? null
                            : "NamespaceName.EntityA";
                    Assert.AreEqual(expectedTypeName, firstEntity.ServerTypeName, "Type names don't match.");

                    // Verify content/@src by checking that the read stream of the entry is correct
                    Assert.AreEqual("http://odata.org/readstream1", firstEntity.ReadStreamUri.OriginalString, "ReadStreams don't match.");

                    // Verify content/@type by making sure the properties were read
                    Assert.AreEqual(1, firstItem.ID, "IDs don't match.");
                });
            }
        }
예제 #7
0
        public void AtomParserNavigationLinkTypeTest()
        {
            const string atomPayloadTemplate =
                @"HTTP/1.1 200 OK
Content-Type: application/atom+xml

<feed xmlns:d='http://docs.oasis-open.org/odata/ns/data' xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>ASet</title>
  <id>http://host/ASet</id>
  <updated>2010-09-01T23:36:00Z</updated>
  <link rel='self' title='ASet' href='ASet' />
  <entry>
    <id>http://host/ASet(1)</id>
    <title type='text'></title>
    <updated>2010-09-01T23:36:00Z</updated>
    <author>
      <name />
    </author>
    <link rel='http://docs.oasis-open.org/odata/ns/related/{0}' title='{0}' href='ASet' type='{1}'>{2}</link>
    <category term='NamespaceName.EntityA' scheme='http://docs.oasis-open.org/odata/ns/scheme' />
    <content type='CustomType/CustomSubType' src='ASet(1)/$value' />
    <m:properties>
      <d:ID m:type='Edm.Int32'>1</d:ID>
    </m:properties>
  </entry>
</feed>";

            var linkTypes = new[]
            {
                new
                {
                    LinkType             = "application/atom+xml;type=entry",
                    ExpectedErrorMessage = new Func <bool, bool?, string>((collection, expandedFeed) =>
                    {
                        // If the link type is entry:
                        // - For collection property, it must fail since the type must match the model
                        // - If the link is expanded the payload must match the model
                        if (collection && expandedFeed == false)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedEntryInFeedNavigationLink"));
                        }
                        if (collection && expandedFeed == true)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedFeedInEntryNavigationLink"));
                        }
                        if (collection)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_DeferredEntryInFeedNavigationLink"));
                        }
                        if (!collection && expandedFeed == true)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedFeedInEntryNavigationLink"));
                        }
                        return(null);
                    }),
                    RecognizedLinkType = true,
                },
                new
                {
                    LinkType             = "application/atom+xml;type=feed",
                    ExpectedErrorMessage = new Func <bool, bool?, string>((collection, expandedFeed) =>
                    {
                        // If the link type is feed:
                        // - If the link is not expanded the type must match the model
                        // - If the link is expanded the expanded payload must match the model as well.
                        if (!collection)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_FeedNavigationLinkForResourceReferenceProperty", "Singleton"));
                        }
                        if (collection && expandedFeed == false)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedEntryInFeedNavigationLink"));
                        }
                        return(null);
                    }),
                    RecognizedLinkType = true,
                },
                new
                {
                    LinkType = "application/atom+xml",
                    // Breaking change: we are not recognizing links even when type is missing
                    // If the link has no type, client doesn't recognize this as a nav. prop.
                    ExpectedErrorMessage = new Func <bool, bool?, string>((collection, expandedFeed) =>
                    {
                        if (!collection && expandedFeed == true)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedFeedInEntryNavigationLink"));
                        }
                        if (collection && expandedFeed == false)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedEntryInFeedNavigationLink"));
                        }
                        return(null);
                    }),
                    RecognizedLinkType = true,
                },
                new
                {
                    LinkType = "application/atom+xml;type=test",
                    // Breaking change: we are not recognizing links even when type is wrong
                    // If the link has wrong type, client doesn't recognize this as a nav. prop.
                    ExpectedErrorMessage = new Func <bool, bool?, string>((collection, expandedFeed) =>
                    {
                        if (!collection && expandedFeed == true)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedFeedInEntryNavigationLink"));
                        }
                        if (collection && expandedFeed == false)
                        {
                            return(ODataLibResourceUtil.GetString("ODataAtomReader_ExpandedEntryInFeedNavigationLink"));
                        }
                        return(null);
                    }),
                    RecognizedLinkType = true,
                },
            };

            string deferredLinkBody  = string.Empty;
            string expandedFeedBody  = "<m:inline><feed/></m:inline>";
            string expandedEntryBody = "<m:inline><entry><id>http://host/ASet</id></entry></m:inline>";

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();

                TestUtil.RunCombinations(
                    new string[] { "Singleton", "Collection" },
                    new bool?[] { null, false, true },
                    linkTypes,
                    (propertyName, expandedFeed, linkType) =>
                {
                    DataServiceContext ctx = new DataServiceContext(request.ServiceRoot);
                    ctx.EnableAtom         = true;

                    string linkBody    = expandedFeed.HasValue ? (expandedFeed == true ? expandedFeedBody : expandedEntryBody) : deferredLinkBody;
                    string atomPayload = string.Format(atomPayloadTemplate, propertyName, linkType.LinkType, linkBody);
                    playbackService.OverridingPlayback = atomPayload;

                    Exception exception = TestUtil.RunCatching(() => ctx.CreateQuery <EntityWithNavigationProperties>("ASet").AsEnumerable().Count());

                    string expectedExceptionMessage = linkType.ExpectedErrorMessage(propertyName == "Collection", expandedFeed);

                    if (exception == null)
                    {
                        Assert.IsNull(expectedExceptionMessage, "The test case was expected to fail.");
                        EntityDescriptor entity = ctx.Entities.Single(e => e.Identity == new Uri("http://host/ASet(1)"));
                        if (linkType.RecognizedLinkType)
                        {
                            Assert.AreEqual(ctx.BaseUri + "/ASet", entity.LinkInfos.Single(li => li.Name == propertyName).NavigationLink.OriginalString);
                        }
                        else
                        {
                            Assert.AreEqual(0, entity.LinkInfos.Count, "The link should not have been recognized as a navigation link.");
                        }
                    }
                    else
                    {
                        Assert.IsNotNull(expectedExceptionMessage, "The test case was expected to succeed.");
                        Assert.AreEqual(expectedExceptionMessage, exception.Message, "Unexpected error.");
                    }
                });
            }
        }
예제 #8
0
        public void AtomParserLinkRelTest()
        {
            const string atomPayloadTemplate =
                @"HTTP/1.1 200 OK
Content-Type: application/atom+xml

<feed xmlns:d='http://docs.oasis-open.org/odata/ns/data' xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' xmlns='http://www.w3.org/2005/Atom'>
  <title type='text'>ASet</title>
  <id>http://host/ASet</id>
  <updated>2010-09-01T23:36:00Z</updated>
  <link rel='self' title='ASet' href='ASet' />
  <entry>
    <id>http://host/ASet(1)</id>
    <title type='text'></title>
    <updated>2010-09-01T23:36:00Z</updated>
    <author>
      <name />
    </author>
    <link rel='{0}' title='EntityA' href='ASet(1)' />
    <link rel='{1}' title='EntityA' href='ASet(1)' />
    <link rel='{2}' title='EntityA' href='ASet(1)/$value' />
    <category term='NamespaceName.EntityA' scheme='http://docs.oasis-open.org/odata/ns/scheme' />
    <content type='CustomType/CustomSubType' src='ASet(1)/$value' />
    <m:properties>
      <d:ID m:type='Edm.Int32'>1</d:ID>
    </m:properties>
  </entry>
</feed>";

            string[] selfLinkRefs      = { "self", "http://www.iana.org/assignments/relation/self" };
            string[] editLinkRefs      = { "edit", "http://www.iana.org/assignments/relation/edit" };
            string[] editMediaLinkRefs = { "edit-media", "http://www.iana.org/assignments/relation/edit-media" };

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();

                TestUtil.RunCombinations(selfLinkRefs, editLinkRefs, editMediaLinkRefs, (self, edit, editMedia) =>
                {
                    DataServiceContext ctx = new DataServiceContext(request.ServiceRoot);
                    ctx.EnableAtom         = true;

                    string atomPayload = string.Format(atomPayloadTemplate, self, edit, editMedia);
                    playbackService.OverridingPlayback = atomPayload;

                    foreach (var item in ctx.CreateQuery <EntityA>("ASet"))
                    {
                        Assert.IsNotNull(item, "item");
                    }

                    EntityDescriptor entity = ctx.Entities.Single();

                    Assert.AreEqual(ctx.BaseUri + "/ASet(1)", entity.SelfLink.OriginalString);
                    Assert.AreEqual(ctx.BaseUri + "/ASet(1)", entity.EditLink.OriginalString);
                    Assert.AreEqual(ctx.BaseUri + "/ASet(1)/$value", entity.ReadStreamUri.OriginalString);
                    Assert.AreEqual(ctx.BaseUri + "/ASet(1)/$value", entity.EditStreamUri.OriginalString);
                });
            }
        }
예제 #9
0
        public void AtomParserDuplicateTest_AfterODataLibIntegration()
        {
            // BUG:
            // The following breaking changes are taken in WCF DS client. Once "ODataLib - WCF DS client" integration is complete, enable the asserts in this test.
            // Current ODataLib behavior for WCF DS client is the following.
            // (a) Read the first entry/category element with a valid scheme even if it does not have a term attribute.
            // (b) Throw if there are duplicate entry/link/m:inline elements
            // (c) For m:properties:
            //      For entry/content/m:properties, the first m:properties in each of the entry/content elements is taken and merged.
            //      For entry/m:properties, all duplicates are read and merged.
            // (d) Relationship links are V3 feature. So, we will change WCF DS client do disallow duplicates.
            // (e) Named streams are V3 feature. If we see two different mime types when processing two named streams with the same name but are read and edit links respectively, we will throw.
            // (f) Named streams are V3 feature. If we see named stream duplicates, we will throw.

            const string atomPayloadTemplate =
                @"HTTP/1.1 200 OK
Content-Type: application/atom+xml

<feed xmlns:d='http://docs.oasis-open.org/odata/ns/data' xmlns:m='http://docs.oasis-open.org/odata/ns/metadata' xmlns='http://www.w3.org/2005/Atom'>
  <m:count>3</m:count>
  <title type='text'>ASet</title>
  <id>http://host/ASet</id>
  <updated>2010-09-01T23:36:00Z</updated>
  <link rel='self' title='ASet' href='ASet' />
  <entry>
    <id>http://host/ASet(2)</id>
    <id>http://host/ASet(1)</id>
    <title type='text'></title>
    <updated>2010-09-01T23:36:00Z</updated>
    <author>
      <name />
    </author>
    {0}
  </entry>
</feed>";
            var tests = new[]
            {
                new
                {
                    Type    = "AssociationLinks",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/relatedlinks/NavProp' href='http://odata.org/link1' type='application/xml'/>
    <link rel='http://docs.oasis-open.org/odata/ns/relatedlinks/NavProp' href='http://odata.org/link2' type='application/xml'/>"
                },
                new
                {
                    Type    = "NavigationLinks",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/related/RelProp' href='http://odata.org/link3' type='application/atom+xml;type=entry'/>
    <link rel='http://docs.oasis-open.org/odata/ns/related/RelProp' href='http://odata.org/link4' type='application/atom+xml;type=entry'/>"
                },
                new
                {
                    // mime types differ for the same stream. This is not allowed.
                    Type    = "NamedStreams",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/mediaresource/NamedStream' href='http://odata.org/readlink' type='mime/type1'/>
    <link rel='http://docs.oasis-open.org/odata/ns/edit-media/NamedStream' href='http://odata.org/editlink' type='mime/type2'/>"
                },
                new
                {
                    Type    = "MediaResourceStreams",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/mediaresource/MediaResourceStreamDup' href='http://odata.org/readlink1' type='mime/type3'/>
    <link rel='http://docs.oasis-open.org/odata/ns/mediaresource/MediaResourceStreamDup' href='http://odata.org/readlink2' type='mime/type4'/>"
                },
                new
                {
                    Type    = "EditMedialinks",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/edit-media/EditMediaStreamDup' href='http://odata.org/editlink1' type='mime/type5'/>
    <link rel='http://docs.oasis-open.org/odata/ns/edit-media/EditMediaStreamDup' href='http://odata.org/editlink2' type='mime/type6'/>"
                },
                new
                {
                    Type    = "Content",
                    Payload = @"    
          <content type='application/xml' >
             <m:properties>
                <d:ID m:type='Edm.Int32'>1</d:ID>
             </m:properties>
          </content>
          <content src='http://odata.org'/>"
                },
                new
                {
                    Type    = "Properties",
                    Payload = @"
    <link rel='http://docs.oasis-open.org/odata/ns/related/B' type='application/atom+xml;type=entry' title='B' href='Customer(1)/B'>
      <m:inline>
        <entry>
          <id>http://host/BSet(1)</id>
          <content type='application/xml' >
             <m:properties>
                <d:ID m:type='Edm.Int32'>1</d:ID>
                <d:Prop1 m:type='Edm.Int32'>1</d:Prop1>
             </m:properties>
             <m:properties>
                <d:Prop1 m:type='Edm.Int32'>2</d:Prop1>
             </m:properties>
          </content>
          <content type='application/xml' >
             <m:properties>
                <d:ID m:type='Edm.Int32'>2</d:ID>
                <d:Prop1 m:type='Edm.Int32'>3</d:Prop1>
             </m:properties>
             <m:properties>
                <d:Prop1 m:type='Edm.Int32'>4</d:Prop1>
             </m:properties>
          </content>
        </entry>      
      </m:inline>
    </link>
    <m:properties>
        <d:ID m:type='Edm.Int32'>1</d:ID>
        <d:Prop1 m:type='Edm.Int32'>1</d:Prop1>
    </m:properties>
    <m:properties>
        <d:Prop1 m:type='Edm.Int32'>2</d:Prop1>
    </m:properties>"
                },
                new
                {
                    Type    = "TypeScheme",
                    Payload = @"
    <category scheme='http://docs.oasis-open.org/odata/ns/scheme' />
    <category scheme='http://docs.oasis-open.org/odata/ns/scheme' term='NamespaceName.EntityB' />"
                },
                new
                {
                    Type    = "InlineElements",
                    Payload = @"
      <link rel='http://docs.oasis-open.org/odata/ns/related/B' type='application/atom+xml;type=entry' title='B' href='Customer(1)/B'>
      <m:inline>
        <entry>
          <id>http://host/BSet(2)</id>
          <content type='application/xml' >
             <m:properties>
                <d:ID m:type='Edm.Int32'>2</d:ID>
                <d:Prop1 m:type='Edm.Int32'>5</d:Prop1>
             </m:properties>
             <m:properties>
                <d:Prop1 m:type='Edm.Int32'>6</d:Prop1>
             </m:properties>
          </content>
        </entry>
      </m:inline>
      <m:inline/>
      </link>"
                },
            };

            string[] shouldFailTests = new string[] { "AssociationLinks", "NamedStreams", "Content", "MediaResourceStreams", "EditMedialinks", "InlineElements", "Properties" };
            string[] shouldPassTests = new string[] { "NavigationLinks", "TypeScheme" };

            PlaybackServiceDefinition playbackService = new PlaybackServiceDefinition();

            using (TestWebRequest request = playbackService.CreateForInProcessWcf())
            {
                request.ServiceType        = typeof(PlaybackService);
                request.ForceVerboseErrors = true;
                request.StartService();

                TestUtil.RunCombinations(tests, (test) =>
                {
                    DataServiceContext ctx = new DataServiceContext(request.ServiceRoot, ODataProtocolVersion.V4);
                    ctx.EnableAtom         = true;

                    playbackService.OverridingPlayback = string.Format(atomPayloadTemplate, test.Payload);
                    EntityA entity = null;
                    Exception ex   = TestUtil.RunCatching(() => entity = ctx.CreateQuery <EntityA>("ASet").FirstOrDefault());

                    if (shouldFailTests.Contains(test.Type))
                    {
                        Assert.IsNotNull(ex, test.Type + " expects an exception but got none.");
                    }
                    else
                    {
                        Assert.IsNull(ex, test.Type + " expects to pass but got an exception: " + ex);
                    }

                    // Duplicate navigation links are allowed.
                    if (test.Type == "NavigationLinks")
                    {
                        EntityDescriptor descriptor = ctx.Entities.First();
                        Assert.AreEqual(1, descriptor.LinkInfos.Count());
                        Assert.AreEqual("http://odata.org/link4", descriptor.LinkInfos[0].NavigationLink.AbsoluteUri);
                    }

                    // Read the first entry/category element with a valid scheme even if the term attribute is not present.
                    if (test.Type == "TypeScheme")
                    {
                        EntityDescriptor descriptor = ctx.Entities.First();
                        Assert.AreEqual(null, descriptor.ServerTypeName);
                    }
                });
            }
        }