WCF Webプログラミングモデルの学習テスト Part4

先日のWCF 配信の学習テストを変更。AtomフィードRSSフィードを使用した POST / GET / PUT / DELETE について。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
using System.Xml;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace LearningWCFTests
{
    [TestClass]
    public class SyndicationFeedTest
    {
        ...

        [ServiceContract]
        public interface ICustomers
        {
            [OperationContract]
            [WebGet(UriTemplate = "customers?format={format}")]
            [ServiceKnownType(typeof(Atom10FeedFormatter))]
            [ServiceKnownType(typeof(Rss20FeedFormatter))]
            SyndicationFeedFormatter GetCustomers(string format);

            [OperationContract]
            [WebGet(UriTemplate = "customers/{id}?format={format}")]
            [ServiceKnownType(typeof(Atom10ItemFormatter))]
            [ServiceKnownType(typeof(Rss20ItemFormatter))]
            SyndicationItemFormatter GetCustomer(string id, string format);

            [OperationContract]
            [WebInvoke(Method = "POST", UriTemplate = "customers?format={format}")]
            [ServiceKnownType(typeof(Atom10ItemFormatter))]
            [ServiceKnownType(typeof(Rss20ItemFormatter))]
            SyndicationItemFormatter AddCustomer(SyndicationItemFormatter customerFormatter, string format);

            [OperationContract]
            [WebInvoke(Method = "PUT", UriTemplate = "customers/{id}?format={format}")]
            [ServiceKnownType(typeof(Atom10ItemFormatter))]
            [ServiceKnownType(typeof(Rss20ItemFormatter))]
            SyndicationItemFormatter UpdateCustomer(string id, SyndicationItemFormatter customerFormatter, string format);

            [OperationContract]
            [WebInvoke(Method = "DELETE", UriTemplate = "customers/{id}")]
            void DeleteCustomer(string id);
        }

        [DataContract]
        public class Customer
        {
            [DataMember]
            public string Id { get; set; }

            [DataMember]
            public string Name { get; set; }

            [DataMember]
            public string Address { get; set; }

            public override string ToString()
            {
                return String.Format("{0}, {1}, {2}", Id, Name, Address);
            }
        }

        [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
        public class Customers : ICustomers
        {
            private int lastId_ = 0;
            private List<Customer> customers_ = new List<Customer>();
            private static object syncRoot_ = new Object();

            public SyndicationFeedFormatter GetCustomers(string format)
            {
                return CreateFeedFormatterFrom(customers_, format);
            }

            public SyndicationItemFormatter GetCustomer(string id, string format)
            {
                if (NotContainsCustomer(id))
                {
                    // HTTP ステータス 404 
                    WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
                    return null;
                }

                return CreateItemFormatterFrom(
                    customers_.First(customer => customer.Id == id), format);
            }

            public SyndicationItemFormatter AddCustomer(SyndicationItemFormatter customerFormatter, string format)
            {
                SyndicationItemFormatter formatter = null;

                lock (syncRoot_)
                {
                    XmlSyndicationContent content = customerFormatter.Item.Content as XmlSyndicationContent;
                    Customer customer = content.ReadContent<Customer>();
                    lastId_++;
                    customer.Id = lastId_.ToString();
                    customers_.Add(customer);
                    formatter = CreateItemFormatterFrom(customer, format);

                    UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
                    UriTemplate template = new UriTemplate("customers/{id}");

                    // HTTP ステータス 201 
                    WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(
                        template.BindByPosition(match.BaseUri, customer.Id));
                }

                return formatter;
            }

            public SyndicationItemFormatter UpdateCustomer(string id, SyndicationItemFormatter customerFormatter, string format)
            {
                SyndicationItemFormatter formatter = null;

                if (NotContainsCustomer(id))
                {
                    // HTTP ステータス 404 
                    WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
                    return null;
                }

                lock (syncRoot_)
                {
                    XmlSyndicationContent content = customerFormatter.Item.Content as XmlSyndicationContent;
                    customers_.Remove(customers_.Single(customer => customer.Id == id));
                    Customer updatedCustomer = content.ReadContent<Customer>();
                    customers_.Add(updatedCustomer);

                    formatter = CreateItemFormatterFrom(updatedCustomer, format);
                }

                return formatter;
            }

            public void DeleteCustomer(string id)
            {
                if (NotContainsCustomer(id))
                {
                    // HTTP ステータス 404 
                    WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
                    return;
                }

                lock (syncRoot_)
                {
                    customers_.Remove(customers_.Single(customer => customer.Id == id));
                }
            }

            private SyndicationFeedFormatter CreateFeedFormatterFrom(IEnumerable<Customer> customers, string format)
            {
                UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
                UriTemplate template = new UriTemplate("customers");

                SyndicationFeed feed = new SyndicationFeed() 
                { 
                    Title = SyndicationContent.CreatePlaintextContent("Customers"),
                    LastUpdatedTime = DateTimeOffset.Now
                };
                feed.Links.Add(new SyndicationLink() 
                    { Uri = template.BindByPosition(match.BaseUri), RelationshipType = "self", Title = "Customers" });
                feed.Items = from customer in customers
                    select GetSyndicationItemFrom(customer);

                if (format == "atom")
                    return new Atom10FeedFormatter(feed);
                else
                    return new Rss20FeedFormatter(feed);
            }

            private SyndicationItemFormatter CreateItemFormatterFrom(Customer customer, string format)
            {
                if (format == "atom")
                    return new Atom10ItemFormatter(GetSyndicationItemFrom(customer));
                else
                    return new Rss20ItemFormatter(GetSyndicationItemFrom(customer));
            }

            private SyndicationItem GetSyndicationItemFrom(Customer customer)
            {
                UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
                UriTemplate template = new UriTemplate("customers/{id}");

                SyndicationItem item = new SyndicationItem()
                {
                    Title = SyndicationContent.CreatePlaintextContent("Customer"),
                    LastUpdatedTime = DateTimeOffset.Now,
                    Content = SyndicationContent.CreateXmlContent(customer)
                };
                SyndicationLink link = new SyndicationLink()
                {
                    Uri = template.BindByPosition(match.BaseUri, customer.Id),
                    RelationshipType = "edit",
                    Title = "Customer"
                };
                item.Links.Add(link);

                return item;
            }

            private bool NotContainsCustomer(string id)
            {
                return customers_.SingleOrDefault(customer => customer.Id == id) == null;
            }
        }

        private static readonly Uri HostingBaseUri = new Uri("http://localhost:8000");
        private static readonly Uri ClientBaseUri = new Uri("http://localhost:8000");
        private WebServiceHost host_;
        private string aliceId_;
        private string bobId_;

        [TestInitialize]
        public void サービスホスティングの開始とCustomerの登録()
        {
            host_ = new WebServiceHost(typeof(Customers), HostingBaseUri);
            host_.Open();

            using (WebChannelFactory<ICustomers> cf = new WebChannelFactory<ICustomers>(ClientBaseUri))
            {
                ICustomers channel = cf.CreateChannel();
                aliceId_ = AddCustomer(
                    new Customer() { Name = "Alice", Address = "123 Pike Place" }, channel, "atom").Id;
                bobId_ = AddCustomer(
                    new Customer() { Name = "Bob", Address = "12323 Lake Shore Drive" }, channel, "atom").Id;
            }
        }

        private Customer AddCustomer(Customer customer, ICustomers channel, string format)
        {
            SyndicationItemFormatter formatter = channel.AddCustomer(
                GetItemFormatterFrom(customer, format), format);
            XmlSyndicationContent content = formatter.Item.Content as XmlSyndicationContent;

            return content.ReadContent<Customer>();
        }

        private SyndicationItemFormatter GetItemFormatterFrom(Customer customer, string format)
        {
            SyndicationItem item = new SyndicationItem()
            {
                Title = SyndicationContent.CreatePlaintextContent("Customer"),
                LastUpdatedTime = DateTimeOffset.Now,
                Content = SyndicationContent.CreateXmlContent(customer)
            };

            if (format == "atom")
                return item.GetAtom10Formatter();
            else
                return item.GetRss20Formatter();
        }

        [TestCleanup]
        public void サービスホスティングの終了()
        {
            if (host_.State != CommunicationState.Closed)
                host_.Close();
        }

        [TestMethod]
        public void Atom形式のフィードからCustomerが追加できるべき()
        {
            Customerが追加できるべき("atom");
        }

        [TestMethod]
        public void RSS形式のフィードからCustomerが追加できるべき()
        {
            Customerが追加できるべき("rss");
        }

        private void Customerが追加できるべき(string feed)
        {
            using (WebChannelFactory<ICustomers> cf = new WebChannelFactory<ICustomers>(ClientBaseUri))
            {
                ICustomers channel = cf.CreateChannel();
                Customer john = AddCustomer(
                    new Customer() { Name = "John", Address = "4545 Wacker Drive" }, channel, feed);

                Assert.AreEqual<string>(string.Format("{0}, John, 4545 Wacker Drive", john.Id), john.ToString());
                SyndicationItemFormatter insertedJohnFormatter = channel.GetCustomer(john.Id, feed);
                XmlSyndicationContent insertedJohnContent = insertedJohnFormatter.Item.Content as XmlSyndicationContent;
                Assert.AreEqual<string>(string.Format("{0}, John, 4545 Wacker Drive", john.Id),
                    insertedJohnContent.ReadContent<Customer>().ToString());
            }
        }

        [TestMethod]
        public void Atom形式で取得したフィードからCustomerの一覧を取得できるべき()
        {
            Customerの一覧を取得できるべき("atom");
        }

        [TestMethod]
        public void RSS形式で取得したフィードからCustomerの一覧を取得できるべき()
        {
            Customerの一覧を取得できるべき("rss");
        }

        private void Customerの一覧を取得できるべき(string format)
        {
            UriTemplate template = new UriTemplate("customers?format={format}");
            XmlReader reader = XmlReader.Create(template.BindByPosition(ClientBaseUri, format).ToString());
            SyndicationFeed feed = SyndicationFeed.Load(reader);

            Assert.AreEqual<string>("Customers", feed.Title.Text);
            Assert.AreEqual<int>(1, feed.Links.Count);
            Assert.AreEqual<string>("Customers", feed.Title.Text);
            Assert.AreEqual<Uri>(new Uri("http://localhost:8000/customers"), feed.Links.First().Uri);
            Assert.AreEqual<string>("self", feed.Links.First().RelationshipType);
            Assert.AreEqual<string>("Customers", feed.Links.First().Title);
            Assert.AreEqual<int>(2, feed.Items.Count());

            UriTemplate customerTemplate = new UriTemplate("customers/{id}");

            Assert.AreEqual<string>("Customer", feed.Items.ElementAt(0).Title.Text);
            Assert.AreEqual<Uri>(customerTemplate.BindByPosition(HostingBaseUri, aliceId_), feed.Items.ElementAt(0).Links.First().Uri);
            Assert.AreEqual<string>("edit", feed.Items.ElementAt(0).Links.First().RelationshipType);
            Assert.AreEqual<string>("Customer", feed.Items.ElementAt(0).Links.First().Title);
            XmlSyndicationContent aliceContent = feed.Items.ElementAt(0).Content as XmlSyndicationContent;
            Customer alice = aliceContent.ReadContent<Customer>();
            Assert.AreEqual<string>(string.Format("{0}, Alice, 123 Pike Place", aliceId_), alice.ToString());

            Assert.AreEqual<string>("Customer", feed.Items.ElementAt(1).Title.Text);
            Assert.AreEqual<Uri>(customerTemplate.BindByPosition(HostingBaseUri, bobId_), feed.Items.ElementAt(1).Links.First().Uri);
            Assert.AreEqual<string>("edit", feed.Items.ElementAt(1).Links.First().RelationshipType);
            Assert.AreEqual<string>("Customer", feed.Items.ElementAt(1).Links.First().Title);
            XmlSyndicationContent bobContent = feed.Items.ElementAt(1).Content as XmlSyndicationContent;
            Customer bob = bobContent.ReadContent<Customer>();
            Assert.AreEqual<string>(string.Format("{0}, Bob, 12323 Lake Shore Drive", bobId_), bob.ToString());
        }

        [TestMethod]
        public void Atom形式で取得したフィードからCustomerを取得できるべき()
        {
            Customerを取得できるべき("atom");
        }

        [TestMethod]
        public void RSS形式で取得したフィードからCustomerを取得できるべき()
        {
            Customerを取得できるべき("rss");
        }

        private void Customerを取得できるべき(string format)
        {
            UriTemplate template = new UriTemplate("customers/{id}?format={format}");
            XmlReader reader = XmlReader.Create(template.BindByPosition(ClientBaseUri, aliceId_, format).ToString());
            SyndicationItem item = SyndicationItem.Load(reader);

            Assert.AreEqual<string>("Customer", item.Title.Text);
            UriTemplate customerTemplate = new UriTemplate("customers/{id}");
            Assert.AreEqual<Uri>(customerTemplate.BindByPosition(HostingBaseUri, aliceId_), item.Links.First().Uri);
            Assert.AreEqual<string>("edit", item.Links.First().RelationshipType);
            Assert.AreEqual<string>("Customer", item.Links.First().Title);
            XmlSyndicationContent content = item.Content as XmlSyndicationContent;
            Customer alice = content.ReadContent<Customer>();
            Assert.AreEqual<string>(string.Format("{0}, Alice, 123 Pike Place", aliceId_), alice.ToString());
        }

        [TestMethod]
        public void Atom形式のフィードからCustomerが更新できるべき()
        {
            Customerが更新できるべき("atom");
        }
        
        [TestMethod]
        public void RSS形式のフィードからCustomerが更新できるべき()
        {
            Customerが更新できるべき("rss");
        }

        private void Customerが更新できるべき(string format)
        {
            using (WebChannelFactory<ICustomers> cf = new WebChannelFactory<ICustomers>(ClientBaseUri))
            {
                ICustomers channel = cf.CreateChannel();
                SyndicationItemFormatter aliceFormatter = channel.GetCustomer(aliceId_, format);
                XmlSyndicationContent aliceContent = aliceFormatter.Item.Content as XmlSyndicationContent;
                Customer alice = aliceContent.ReadContent<Customer>();
                alice.Name = "Charlie";

                XmlSyndicationContent charlieContent = channel.UpdateCustomer(
                    aliceId_, GetItemFormatterFrom(alice, format), format).Item.Content as XmlSyndicationContent;
                Customer charlie = charlieContent.ReadContent<Customer>();
                Assert.AreEqual<string>(string.Format("{0}, Charlie, 123 Pike Place", charlie.Id), charlie.ToString());

                SyndicationItemFormatter updatedCharlieformatter = channel.GetCustomer(charlie.Id, format);
                XmlSyndicationContent updatedCharlieContent = updatedCharlieformatter.Item.Content as XmlSyndicationContent;
                Assert.AreEqual<string>(string.Format("{0}, Charlie, 123 Pike Place", charlie.Id),
                    updatedCharlieContent.ReadContent<Customer>().ToString());
            }
        }

        [TestMethod]
        [ExpectedException(typeof(EndpointNotFoundException))]
        public void Customerが削除できるべき()
        {
            using (WebChannelFactory<ICustomers> cf = new WebChannelFactory<ICustomers>(ClientBaseUri))
            {
                ICustomers channel = cf.CreateChannel();
                channel.DeleteCustomer(aliceId_);
                channel.GetCustomer(aliceId_, "atom");
            }
        }
    }
}