public void Load_ValidationErrors()
        {
            var mockDomainClient = new CitiesMockDomainClient();

            ValidationResult[] validationErrors = new ValidationResult[] { new ValidationResult("Foo", new string[] { "Bar" }) };
            mockDomainClient.QueryCompletedResult = Task.FromResult(new QueryCompletedResult(Enumerable.Empty <Entity>(), Enumerable.Empty <Entity>(), 0, validationErrors));
            Cities.CityDomainContext ctx = new Cities.CityDomainContext(mockDomainClient);
            string myState = "Test User State";

            LoadOperation <Cities.City> loadOperation = ctx.Load(ctx.GetCitiesQuery(), LoadBehavior.RefreshCurrent, l => l.MarkErrorAsHandled(), myState);;

            this.EnqueueCompletion(() => loadOperation);
            EnqueueCallback(delegate
            {
                Assert.AreSame(myState, loadOperation.UserState);

                CollectionAssert.AreEqual(validationErrors, (ICollection)loadOperation.ValidationErrors);

                // verify the exception properties
                var ex = loadOperation.Error as DomainOperationException;
                Assert.IsNotNull(ex, "expected exception of type DomainOperationException");
                Assert.AreEqual(OperationErrorStatus.ValidationFailed, ex.Status);
                Assert.AreEqual(string.Format(Resource.DomainContext_LoadOperationFailed_Validation, "GetCities"), ex.Message);
            });

            EnqueueTestComplete();
        }
        public async Task Load_Cancel_DomainClientCancel()
        {
            var mockDomainClient = new CitiesMockDomainClient();

            Cities.CityDomainContext ctx = new Cities.CityDomainContext(mockDomainClient);

            // If cancellation results in request beeing cancelled the result should be cancelled
            var tcs = new TaskCompletionSource <QueryCompletedResult>();

            mockDomainClient.QueryCompletedResult = tcs.Task;
            var loadOp = ctx.Load(ctx.GetCitiesQuery());

            loadOp.Cancel();

            Assert.IsTrue(loadOp.IsCancellationRequested);
            Assert.IsTrue(ctx.IsLoading);
            Assert.IsFalse(loadOp.IsCanceled);
            Assert.IsFalse(loadOp.IsComplete);

            tcs.TrySetCanceled(loadOp.CancellationToken);
            await loadOp;

            Assert.IsFalse(ctx.IsLoading);
            Assert.IsTrue(loadOp.IsCanceled);
            Assert.IsTrue(loadOp.IsComplete);
            Assert.IsFalse(loadOp.HasError, "Cancelled operation should not have any error");
        }
        public void ExogenousDomainClient()
        {
            Cities.CityDomainContext ctxt1 = new CityDomainContext(TestURIs.Cities);
            Cities.CityDomainContext ctxt2 = new CityDomainContext(TestURIs.Cities);

            var q1 = ctxt1.GetCitiesInStateQuery("OH");

            ExceptionHelper.ExpectInvalidOperationException(delegate
            {
                ctxt2.Load(q1, false);
            }, string.Format(Resource.DomainContext_InvalidEntityQueryDomainClient, q1.QueryName));
        }
        public async Task Load()
        {
            Cities.CityDomainContext ctx = new Cities.CityDomainContext(new CitiesMockDomainClient());
            string myState = "Test User State";

            var           query = ctx.GetCitiesQuery().Where(p => p.StateName == "WA").OrderBy(p => p.CountyName).Take(4);
            LoadOperation lo    = ctx.Load(query, TestHelperMethods.DefaultOperationAction, myState);

            await lo;

            Assert.IsNull(lo.Error);
            Assert.AreEqual(4, ctx.Cities.Count);
            Assert.IsTrue(ctx.Cities.All(p => p.StateName == "WA"));
            Assert.AreEqual(myState, lo.UserState);
        }
        public void CancellationSupport()
        {
            var domainClient = new CitiesMockDomainClient();

            domainClient.SetSupportsCancellation(false);
            Cities.CityDomainContext ctx = new Cities.CityDomainContext(domainClient);

            var           query = ctx.GetCitiesQuery();
            LoadOperation lo    = ctx.Load(query, false);

            Assert.IsFalse(lo.CanCancel, "Cancellation should not be supported.");
            Assert.IsFalse(ctx.DomainClient.SupportsCancellation, "Cancellation should not be supported.");

            ExceptionHelper.ExpectException <NotSupportedException>(delegate
            {
                lo.Cancel();
            }, string.Format(CultureInfo.CurrentCulture, Resources.AsyncOperation_CancelNotSupported));
        }
        public void Cities_LoadStates_TestEnums()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);

            SubmitOperation so = null;
            LoadOperation lo = dp.Load(dp.GetStatesQuery().Where(s => s.TimeZone == Cities.TimeZone.Pacific), false);

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() =>
            {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);

                // verify the TimeZones were serialized to the client properly
                State state = dp.States.Single(p => p.Name == "WA");
                Assert.AreEqual(Cities.TimeZone.Pacific, state.TimeZone);

                Assert.IsFalse(dp.States.Any(p => p.Name == "OH"));

                // Now test update
                state.TimeZone = state.TimeZone = Cities.TimeZone.Central;
                Assert.AreEqual(EntityState.Modified, state.EntityState);

                EntityChangeSet cs = dp.EntityContainer.GetChanges();
                Assert.IsTrue(cs.ModifiedEntities.Contains(state));

                so = dp.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(() =>
            {
                TestHelperMethods.AssertOperationSuccess(so);
            });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_ValidationErrorOnServer()
        {
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);
            Zip newZip = null;

            SubmitOperation so = null;
            LoadOperation lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);

            // wait for Load to complete, then invoke domain method that throws on server. Submit.
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                // Add an entity that will cause a Validation exception on the server (99999 is used as a way to signal failure for our validator)
                newZip = new Zip()
                {
                    Code = 99999,
                    FourDigit = 8625,
                    CityName = "Redmond",
                    CountyName = "King",
                    StateName = "WA"
                };
                citiesProvider.Zips.Add(newZip);
                newZip.ThrowException("InvalidOperationException");

                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });

            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainOperationException ex = so.Error as DomainOperationException;
                Assert.IsNotNull(ex);
                Assert.AreEqual(OperationErrorStatus.ValidationFailed, ex.Status);
                Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Validation, ex.Message);

                IEnumerable<ValidationResult> errors = newZip.ValidationErrors;
                LogErrorListContents("newZip.ValidationErrors", errors);
                Assert.AreEqual(1, errors.Count());
                UnitTestHelper.AssertListContains<ValidationResult>(errors, (e => e.ErrorMessage == "Server fails validation"));
            });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_ErrorsClearOnReject()
        {
            int refZip = 0;
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            SubmitOperation so = null;
            LoadOperation lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);

            // wait for Load to complete, then invoke domain method that throws on server. Submit.
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                // invoke methods that cause exception
                Zip[] zips = citiesProvider.Zips.ToArray();
                zips[0].ThrowException("ValidationException");
                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });

            // wait for submitted event being fired and verify Entity.ValidationErrors is not empty
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainOperationException ex = so.Error as DomainOperationException;
                Assert.IsNotNull(ex);

                Zip zip = citiesProvider.Zips.First();
                IEnumerable<ValidationResult> errors = zip.ValidationErrors;
                LogErrorListContents("zips[0].ValidationErrors", errors);
                Assert.AreEqual(1, errors.Count());

                // Verify that failed submission does not clear out the last invocation
                Assert.IsFalse(zip.CanThrowException);
                Assert.IsTrue(zip.EntityActions.Any(a => a.Name == "ThrowException"));

                // Add a custom validation error to ensure it gets cleared
                zip.ValidationErrors.Add(new ValidationResult("Temporary Error"));

                // Call RejectChanges and verify ValidationErrors collection is cleared
                citiesProvider.RejectChanges();
                Assert.IsFalse(zip.ValidationErrors.Any());

                // Invoke domain method that does not throw on same entity
                zip.ReassignZipCode(1, true);
                refZip = zip.Code;
                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });

            // wait for submitted event being fired and verify Entity.ValidationErrors remains empty
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Zip zip = citiesProvider.Zips.First();
                Assert.IsNull(so.Error);
                Assert.IsFalse(zip.ValidationErrors.Any());
                Assert.AreEqual(refZip + 1, zip.Code);
            });

            EnqueueTestComplete();
        }
        public void Bug706066_CancelInCallback()
        {
            Cities.CityDomainContext cities = new CityDomainContext(TestURIs.Cities);

            bool callbackCalled = false;
            InvalidOperationException expectedException = null;
            Action<LoadOperation<City>> callback = (op) =>
            {
                if (op.HasError)
                {
                    op.MarkErrorAsHandled();
                }

                // verify that CanCancel is false even though we'll
                // ignore this and try below
                Assert.IsFalse(op.CanCancel);

                try
                {
                    op.Cancel();
                }
                catch (InvalidOperationException io)
                {
                    expectedException = io;
                }
                callbackCalled = true;
            };

            var q = cities.GetCitiesQuery().Take(1);
            LoadOperation lo = cities.Load(q, callback, null);

            EnqueueConditional(() => lo.IsComplete && callbackCalled);
            EnqueueCallback(delegate
            {
                Assert.IsFalse(lo.IsCanceled);
                Assert.AreEqual(Resources.AsyncOperation_AlreadyCompleted, expectedException.Message);
            });
            EnqueueTestComplete();
        }
        public void Bug706034_AccessCachedEntityResultsInCallback()
        {
            Cities.CityDomainContext cities = new CityDomainContext(TestURIs.Cities);

            bool callbackCalled = false;
            Exception callbackException = null;
            Action<LoadOperation<City>> callback = (op) =>
            {
                if (op.HasError)
                {
                    op.MarkErrorAsHandled();
                }

                try
                {
                    Assert.AreEqual(11, op.AllEntities.Count());
                    Assert.AreEqual(11, op.Entities.Count());
                }
                catch (Exception e)
                {
                    callbackException = e;
                }
                finally
                {
                    callbackCalled = true;
                }
            };

            var q = cities.GetCitiesQuery();
            LoadOperation<City> lo = cities.Load(q, callback, null);

            // KEY to bug : access Entity collections to force them to cache
            IEnumerable<City> entities = lo.Entities;
            IEnumerable<Entity> allEntities = lo.AllEntities;

            EnqueueConditional(() => lo.IsComplete && callbackCalled);
            EnqueueCallback(delegate
            {
                Assert.IsNull(callbackException);
                Assert.IsNull(lo.Error);

                Assert.AreEqual(11, lo.AllEntities.Count());
                Assert.AreEqual(11, lo.Entities.Count());
            });
            EnqueueTestComplete();
        }
        public void Cities_TestLoad_Demo()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    // Abs URI so runs on desktop too

            LoadOperation lo = dp.Load(dp.GetCitiesQuery(), false);

            After(() => lo.IsComplete);

            Then(() => {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
            });

            Then(() => AssertSame(new CityData().Cities, dp.Cities));

            EnqueueTestComplete();
        }
        public void ErrorPipeline_DomainServiceEx()
        {
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            LoadOperation lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);
            SubmitOperation so = null;

            // wait for Load to complete, then invoke domain method that throws on server. Submit.
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                Zip zip = citiesProvider.Zips.First();
                zip.ThrowException("DomainServiceExceptionWithErrorCode");
                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainException ex = so.Error as DomainException;
                Assert.IsNotNull(ex);
                Assert.AreEqual("testing with error code", ex.Message);
                Assert.AreEqual(10, ex.ErrorCode);

            });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_DomainMethodsThrow_EntityValidationError()
        {
            List<string>[] propChanged = new List<string>[] { new List<string>(), new List<string>(), new List<string>() };
            int refZip = 0;

            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);
            SubmitOperation so = null;
            LoadOperation lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);

            // wait for Load to complete, then invoke some domain methods
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                // invoke methods that cause exception
                Zip[] zips = citiesProvider.Zips.ToArray();
                citiesProvider.ThrowException(zips[0], "EntityValidationException");
                citiesProvider.ThrowException(zips[1], "EntityValidationException");

                // invoke method that does not cause exception
                zips[2].ReassignZipCode(1, true);
                refZip = zips[2].Code;

                zips[0].PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    propChanged[0].Add(e.PropertyName);
                };
                zips[1].PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    propChanged[1].Add(e.PropertyName);
                };
                zips[2].PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    propChanged[2].Add(e.PropertyName);
                };

                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Assert.IsNotNull(so.Error);
                DomainOperationException ex = so.Error as DomainOperationException;

                // this is a case where method invocations causes a ValidationException.
                Assert.AreEqual(OperationErrorStatus.ValidationFailed, ex.Status);
                Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Validation, ex.Message);

                // verify ValidationError collection is correct
                Zip[] zips = citiesProvider.Zips.ToArray();
                IEnumerable<ValidationResult> errors = zips[0].ValidationErrors;
                LogErrorListContents("citiesProvider.Zips[0].ValidationErrors", errors);
                Assert.AreEqual(1, errors.Count());
                ValidationResult vr = errors.First();
                Assert.AreEqual("Invalid Zip properties!", vr.ErrorMessage);
                Assert.AreEqual(2, vr.MemberNames.Count());
                Assert.IsTrue(vr.MemberNames.Contains("CityName"));
                Assert.IsTrue(vr.MemberNames.Contains("CountyName"));

                errors = zips[1].ValidationErrors;
                LogErrorListContents("citiesProvider.Zips[0].ValidationErrors", errors);
                Assert.AreEqual(1, errors.Count());
                vr = errors.First();
                Assert.AreEqual("Invalid Zip properties!", vr.ErrorMessage);
                Assert.AreEqual(2, vr.MemberNames.Count());
                Assert.IsTrue(vr.MemberNames.Contains("CityName"));
                Assert.IsTrue(vr.MemberNames.Contains("CountyName"));

                // verify the Entity.ValidationErrors collection is populated as expected
                Assert.IsTrue(propChanged[0].Contains("HasValidationErrors"));
                Assert.IsTrue(propChanged[1].Contains("HasValidationErrors"));

                // verify entities are not auto-synced back to the client because there were errors
                Assert.IsFalse(propChanged[2].Contains("Code"));
                Assert.IsFalse(propChanged[2].Contains("FourDigit"));
                Assert.IsFalse(propChanged[2].Contains("HasValidationErrors"));
                Assert.AreEqual(0, zips[2].ValidationErrors.Count());
                Assert.AreEqual(refZip, zips[2].Code);
            });

            EnqueueTestComplete();
        }
        public void Inherit_Run_Call_Derived_Custom_Method_On_Abstract_Base_()
        {
            // Inheritance is City <-- CityWithEditHistory <-- CityWithInfo
            // This test invokes a custom method declared on CityWithEditHistory via a CityWithInfo instance
            EntityChangeSet changeset;
            List<string> propChanged = new List<string>();
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);
            CityWithInfo cityWithInfo = null;
            DateTime priorLastUpdated = DateTime.Now;

            LoadOperation lo = citiesProvider.Load(citiesProvider.GetCitiesWithInfoQuery());
            SubmitOperation so = null;

            // wait for Load to complete, then invoke some domain methods
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                cityWithInfo = citiesProvider.Cities.FirstOrDefault() as CityWithInfo;
                Assert.IsNotNull(cityWithInfo, "Cities[0] should have been CityWithInfo but was " + citiesProvider.Cities.First().GetType().Name);
                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.IsTrue(changeset.IsEmpty);
                cityWithInfo.PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    // We filter to only those properties we see in the City hierarchy
                    BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
                    bool isCityProperty = (typeof(CityWithInfo).GetProperty(e.PropertyName, flags) != null ||
                                           typeof(CityWithEditHistory).GetProperty(e.PropertyName, flags) != null ||
                                           typeof(City).GetProperty(e.PropertyName, flags) != null);
                    if (isCityProperty)
                    {
                        propChanged.Add(e.PropertyName);
                    }
                };

                priorLastUpdated = cityWithInfo.LastUpdated;
                Assert.IsTrue(cityWithInfo.CanTouchHistory);
                cityWithInfo.TouchHistory("xxx");
            });

            // wait for prop changed for domain method guards
            EnqueueConditional(() => propChanged.Count > 0);

            // Inject small delay so DateTime.Now executed on server is later than value in cithWithInfo
            EnqueueDelay(50);

            // Test validation that we will, in fact, get a different time stamp
            EnqueueCallback(delegate
            {
                Assert.AreNotEqual(priorLastUpdated, DateTime.Now, "Expected difference in times after small delay");
            });

            EnqueueCallback(delegate
            {
                Assert.IsTrue(propChanged.Contains("CanTouchHistory"));
                propChanged.Clear();

                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.AreEqual(1, changeset.ModifiedEntities.Count);

                so = citiesProvider.SubmitChanges();

                Assert.AreEqual(1, so.ChangeSet.ModifiedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.AddedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.RemovedEntities.Count);
            });
            // wait for submit to complete, then verify invokedEntities in changeset
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Assert.IsNull(so.Error);
                Assert.AreEqual(1, so.ChangeSet.ModifiedEntities.Count);

                // verify we got the property change notification for the city entity as a result of autosync
                Assert.AreEqual(12, propChanged.Count, "Received different property notifications than expected:\r\n" + string.Join(",", propChanged.ToArray()));
                Assert.AreEqual(1, propChanged.Count(prop => prop =="EditHistory"));
                Assert.AreEqual(1, propChanged.Count(prop => prop =="LastUpdated"));
                Assert.AreEqual(2, propChanged.Count(prop => prop =="CanAssignCityZone"));
                Assert.AreEqual(2, propChanged.Count(prop => prop =="CanAssignCityZoneIfAuthorized"));
                Assert.AreEqual(2, propChanged.Count(prop => prop =="CanAutoAssignCityZone"));
                Assert.AreEqual(1, propChanged.Count(prop => prop =="CanTouchHistory"));
                Assert.AreEqual(1, propChanged.Count(prop => prop =="IsTouchHistoryInvoked"));
                Assert.AreEqual(2, propChanged.Count(prop => prop =="CanSetCityInfo"));

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                CityWithInfo newCityWithInfo = citiesProvider.Cities.OfType<CityWithInfo>().SingleOrDefault<CityWithInfo>(c => (c.EditHistory.Contains("touch")));
                Assert.IsNotNull(newCityWithInfo, "Did not find modified CityWithInfo after the submit");
                Assert.AreNotEqual(newCityWithInfo.LastUpdated, priorLastUpdated, "Expected lastUpdated to be modified by submit");
                Assert.IsTrue(newCityWithInfo.EditHistory.Contains("touch=xxx"), "EditHistory was" + newCityWithInfo.EditHistory);
            });

            EnqueueTestComplete();
        }
        public void Inherit_Run_Call_Base_Custom_Method_On_Derived_Entity()
        {
            EntityChangeSet changeset;
            List<string> propChanged = new List<string>();
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            LoadOperation lo = citiesProvider.Load(citiesProvider.GetCitiesQuery());
            SubmitOperation so = null;

            CityWithInfo cityWithInfo = null;

            // wait for Load to complete, then invoke some domain methods
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.IsTrue(changeset.IsEmpty);

                cityWithInfo = citiesProvider.Cities.OfType<CityWithInfo>().FirstOrDefault();
                Assert.IsNotNull(cityWithInfo, "Expected to find a CityWithInfo type in entity list");

                cityWithInfo.PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    // We filter to only those properties we see in the City hierarchy
                    BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
                    bool isCityProperty = (typeof(CityWithInfo).GetProperty(e.PropertyName, flags) != null ||
                                           typeof(CityWithEditHistory).GetProperty(e.PropertyName, flags) != null ||
                                           typeof(City).GetProperty(e.PropertyName, flags) != null);
                    if (isCityProperty)
                    {
                        propChanged.Add(e.PropertyName);
                    }
                };

                Assert.IsTrue(cityWithInfo.CanAssignCityZone);
                cityWithInfo.AssignCityZone("Zone15");
            });

            // wait for prop changed for domain method guards
            EnqueueConditional(() => propChanged.Count > 0);
            EnqueueCallback(delegate
            {
                Assert.IsTrue(propChanged.Contains("CanAssignCityZone"));
                propChanged.Clear();

                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.AreEqual(1, changeset.ModifiedEntities.Count);
                so = citiesProvider.SubmitChanges();

                Assert.AreEqual(1, so.ChangeSet.ModifiedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.AddedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.RemovedEntities.Count);
            });
            // wait for submit to complete, then verify invokedEntities in changeset
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                if (so.Error != null)
                {
                    Assert.Fail(so.Error.Message);
                }
                Assert.AreEqual(1, so.ChangeSet.ModifiedEntities.Count);
                // verify we got the property change notification for the city entity as a result of autosync
                Assert.AreEqual(13, propChanged.Count, "Received different property notifications than expected:\r\n" + string.Join(",", propChanged.ToArray()));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "EditHistory"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "ZoneName"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "ZoneID"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "CanAssignCityZone"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "IsAssignCityZoneInvoked"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanAutoAssignCityZone"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanAssignCityZoneIfAuthorized"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanSetCityInfo"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanTouchHistory"));

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                Assert.AreEqual(15, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Zone15")).ZoneID);

                // verify unchanged entities
                Assert.AreEqual(0, citiesProvider.Cities.First(c => (c.ZoneName == null)).ZoneID);
            });

            EnqueueTestComplete();
        }
        public void Cities_ShouldSupportLongQueries()
        {
            LoadOperation<Zip> lo = null;
            const int zipToFind = 98053;
            const int QUERY_ITERATIONS = 50;

            EnqueueCallback(() =>
            {
                CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    // Abs URI so runs on desktop too

                // Generate a really long query
                // The load will result in a query where just the query part has length > 3000
                var query = dp.GetZipsQuery();

                // Create a query with QUERY_ITERATIONS where statements checking a range of QUERY_ITERATIONS each
                // this should in the end if simplified result in Code = zipToFind (zipToFind - 1 < Code  <= zipToFind)
                for (int i = 0; i < QUERY_ITERATIONS; ++i)
                {
                    int min = zipToFind + i - QUERY_ITERATIONS;
                    int max = zipToFind + i;
                    query = query.Where(c => min < c.Code && c.Code <= max);
                }

                lo = dp.Load(query, false);
            });

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() =>
            {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
                var expected = new CityData().Zips.Single(z => z.Code == zipToFind);
                Assert.AreEqual(1, lo.Entities.Count(), "Wrong number of entities returned");
                var returned = lo.Entities.Single();

                Assert.AreEqual(expected.Code, returned.Code);
                Assert.AreEqual(expected.FourDigit, returned.FourDigit); 
                Assert.AreEqual(expected.CityName, returned.CityName);
                Assert.AreEqual(expected.CountyName, returned.CountyName);
                Assert.AreEqual(expected.StateName, returned.StateName);
            });
            EnqueueTestComplete();
        }
        public void Cities_Cities_In_County_Serialized_Query()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    // Abs URI so runs on desktop too

            // Pass the query to the server to select only cities in King county
            var cityQuery = dp.GetCitiesQuery().Where(c => c.CountyName == "King");
            LoadOperation lo = dp.Load(cityQuery, false);

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() =>
            {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
                IEnumerable<City> expected = new CityData().Cities.Where(c => c.CountyName == "King");
                AssertSame(expected, dp.Cities);
            });

            EnqueueTestComplete();
        }
        public void TestEmptyStringParameter()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    

            LoadOperation lo = dp.Load(dp.GetCitiesInStateQuery(string.Empty), false);

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() =>
            {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
                Assert.AreEqual(0, dp.Cities.Count);
            });

            EnqueueTestComplete();
        }
        public void Cities_Cities_In_State_Parameterized_Query()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    // Abs URI so runs on desktop too

            LoadOperation lo = dp.Load(dp.GetCitiesInStateQuery("WA"), false);

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() =>
            {
                if (lo.Error != null)
                    Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
                IEnumerable<City> expected = new CityData().Cities.Where(c => c.StateName.Equals("WA"));
                AssertSame(expected, dp.Cities);

                // Validate a [Editable(false)] property deserialized properly
                foreach (City c in dp.Cities)
                    Assert.AreEqual(c.CountyName, c.CalculatedCounty);
           });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_CUDOperationMethodValidation()
        {
            CityDomainContext ctxt = new CityDomainContext(TestURIs.Cities);

            LoadOperation lo = ctxt.Load(ctxt.GetCitiesQuery(), false);
            SubmitOperation so = null;

            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                TestHelperMethods.AssertOperationSuccess(lo);

                // update
                City[] cities = ctxt.Cities.Where(p => p.GetType() == typeof(City)).ToArray();
                City city = cities[0];
                city.ZoneID = 693;

                // custom method
                city.AssignCityZone("Z1");

                // delete
                City deletedCity = new City() { Name = "Issaquah", CountyName = "King", StateName = "WA", ZoneID = 693 };
                ctxt.Cities.Attach(deletedCity);
                ctxt.Cities.Remove(deletedCity);

                // insert
                City newCity = new City() { Name = "Sylvannia", CountyName = "Lucas", StateName = "OH" };
                newCity.ZoneID = 693;
                ctxt.Cities.Add(newCity);

                so = ctxt.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainOperationException ex = so.Error as DomainOperationException;
                Assert.IsNotNull(ex);
                Assert.AreEqual(OperationErrorStatus.ValidationFailed, ex.Status);
                Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Validation, ex.Message);

                EntityChangeSet cs = so.ChangeSet;

                City city = (City)cs.AddedEntities.Single();
                Assert.AreEqual("CityMethodValidator.ValidateMethod Failed (InsertCity)!", city.ValidationErrors.Single().ErrorMessage);

                city = (City)cs.ModifiedEntities.Single();
                ValidationResult[] validationResults = city.ValidationErrors.ToArray();
                Assert.AreEqual("CityMethodValidator.ValidateMethod Failed (UpdateCity)!", validationResults[0].ErrorMessage);
                Assert.AreEqual("CityMethodValidator.ValidateMethod Failed (AssignCityZone)!", validationResults[1].ErrorMessage);

                city = (City)cs.RemovedEntities.Single();
                Assert.AreEqual("CityMethodValidator.ValidateMethod Failed (DeleteCity)!", city.ValidationErrors.Single().ErrorMessage);
            });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_ValidationErrorDuringClientSubmit()
        {
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);
            Zip newZip = new Zip() { Code = 98765, FourDigit = 1234 };
            Zip validZip = new Zip() { Code = 90000, FourDigit = 1000, CityName = "MyCity", StateName = "MY" };
            City deletedCity = null;

            SubmitOperation so = null;
            LoadOperation loadCitiesOperation = citiesProvider.Load(citiesProvider.GetCitiesQuery(), false);
            LoadOperation loadZipsOperation = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);

            // wait for Load to complete, then invoke domain method that throws on server. Submit.
            EnqueueConditional(() => loadCitiesOperation.IsComplete && loadZipsOperation.IsComplete);
            EnqueueCallback(delegate
            {
                // update entity in a way that caused entity validation to fail on client
                Zip[] zips = citiesProvider.Zips.ToArray();
                zips[0].CityName = zips[0].StateName;

                // internally set domain method invocation to cause method param validation to fail on client
                zips[0].CustomMethodInvocation = new EntityAction("ReassignZipCode", new object[] { -10000, true });

                // insert entity that caused object/property validation to fail on client
                citiesProvider.Zips.Add(newZip);

                // Add a temporary error to that invalid object to ensure errors are reset during submit
                newZip.ValidationErrors.Add(new ValidationResult("Temporary Error", new string[] { "StateName" }));

                // insert entity that is valid
                citiesProvider.Zips.Add(validZip);

                // Add a temporary error to that valid object to ensure errors are reset during submit
                validZip.ValidationErrors.Add(new ValidationResult("Temporary Error", new string[] { "StateName" }));

                // remove city
                City[] cities = citiesProvider.Cities.ToArray();
                deletedCity = cities[1];
                citiesProvider.Cities.Remove(deletedCity);

                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });

            // wait for submitted event being fired and verify Entity.ValidationErrors is not empty
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainOperationException ex = so.Error as DomainOperationException;
                Assert.IsNotNull(ex);
                Assert.AreEqual(OperationErrorStatus.ValidationFailed, ex.Status);
                Assert.AreEqual(Resource.DomainContext_SubmitOperationFailed_Validation, ex.Message);

                // verify errors are generated on the client side
                Zip[] zips = citiesProvider.Zips.ToArray();
                IEnumerable<ValidationResult> errors = zips[0].ValidationErrors;
                LogErrorListContents("citiesProvider.Zips[0].ValidationErrors", errors);
                Assert.AreEqual(2, errors.Count());
                UnitTestHelper.AssertListContains<ValidationResult>(errors, (e => e.ErrorMessage == "The field offset must be between -9999 and 9999."));
                UnitTestHelper.AssertListContains<ValidationResult>(errors, (e => e.ErrorMessage == "Zip codes cannot have matching city and state names" && e.MemberNames.Contains("StateName") && e.MemberNames.Contains("CityName")));

                LogErrorListContents("newZip.ValidationErrors", newZip.ValidationErrors);
                errors = newZip.ValidationErrors;

                // Expect only 2 errors for the properties.  The entity level error is not checked if property level checks fail
                Assert.AreEqual(2, errors.Count());
                UnitTestHelper.AssertListContains<ValidationResult>(errors, (e => e.ErrorMessage == "The CityName field is required."));
                UnitTestHelper.AssertListContains<ValidationResult>(errors, (e => e.ErrorMessage == "The StateName field is required."));

                Assert.AreEqual(0, deletedCity.ValidationErrors.Count(), "The deleted city shouldn't have any validation errors");
                Assert.AreEqual(0, validZip.ValidationErrors.Count(), "The valid city shouldn't have any validation errors");
            });

            EnqueueTestComplete();
        }
        public void CustomMethodFlag_ChangeNotifications()
        {
            List<string> propChanged = new List<string>();
            CityDomainContext ctxt = new CityDomainContext(TestURIs.Cities);
            City city = null;

            LoadOperation lo = ctxt.Load(ctxt.GetCitiesQuery(), false);
            SubmitOperation so = null;

            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                city = ctxt.Cities.First();

                city.PropertyChanged += (s, e) =>
                {
                    propChanged.Add(e.PropertyName);
                };

                Assert.IsTrue(city.CanAssignCityZone);
                Assert.IsFalse(city.IsAssignCityZoneInvoked);

                city.AssignCityZone("Twilight");

                Assert.IsFalse(city.CanAssignCityZone);
                Assert.IsTrue(city.IsAssignCityZoneInvoked);

                Assert.IsTrue(propChanged.Contains("CanAssignCityZone"));
                Assert.IsTrue(propChanged.Contains("IsAssignCityZoneInvoked"));
                propChanged.Clear();

                so = ctxt.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Assert.IsFalse(so.HasError);

                Assert.IsTrue(city.CanAssignCityZone);
                Assert.IsFalse(city.IsAssignCityZoneInvoked);

                Assert.IsTrue(propChanged.Contains("CanAssignCityZone"));
                Assert.IsTrue(propChanged.Contains("IsAssignCityZoneInvoked"));
            });
            EnqueueTestComplete();
        }
        public void ErrorPipeline_UnauthorizedEx()
        {
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            SubmitOperation so = null;
            LoadOperation lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);

            // wait for Load to complete, then invoke domain method that throws on server. Submit.
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                Zip[] zips = citiesProvider.Zips.ToArray();
                citiesProvider.Zips.Remove(zips[1]);
                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
            });

            // wait for submitted event being fired and verify submittedEventArgs.Error is not null
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                DomainOperationException error = so.Error as DomainOperationException;
                Assert.IsNotNull(error);
                Assert.AreEqual(string.Format(Resource.DomainContext_SubmitOperationFailed, "Access to operation 'DeleteZip' was denied."), error.Message);
            });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_DomainMethodAndCRUD()
        {
            List<string> propChanged_addedCity = new List<string>();
            City newCity = new City { Name = "Sammamish", CountyName = "King", StateName = "WA" };
            int refZipCode = 0;
            int refCityCount = 0;

            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            LoadOperation lo = citiesProvider.Load(citiesProvider.GetCitiesQuery(), false);
            SubmitOperation so = null;

            // wait for LoadCities to complete, then LoadZips
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                refCityCount = citiesProvider.Cities.Count;
                lo = citiesProvider.Load(citiesProvider.GetZipsQuery(), false);
            });

            // wait for Load to complete, then invoke some domain methods
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                // this test the following combinations of CRUD and Domain method in changeset:
                // - Invoke -> update
                // - Add -> invoke
                // - Remove
                // - Invoke only
                City[] cities = citiesProvider.Cities.ToArray();
                cities[0].ZoneName = "Zone44";
                cities[0].AssignCityZone("Zone1");
                cities[1].AssignCityZone("Zone2");
                citiesProvider.Cities.Add(newCity);
                newCity.AssignCityZone("Zone3");
                citiesProvider.Cities.Remove(cities[4]);
                citiesProvider.Cities.Remove(cities[5]);

                // keep a reference zip code before invoking the method: this will increment Code by offset=1 on the server
                Zip zip = citiesProvider.Zips.First();
                refZipCode = zip.Code;
                zip.ReassignZipCode(1, true);

                newCity.PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    if (e.PropertyName != "IsReadOnly" && e.PropertyName != "HasChanges"
                        && e.PropertyName != "EntityState" && e.PropertyName != "ValidationErrors")
                    {
                        propChanged_addedCity.Add(e.PropertyName);
                    }
                };

                Assert.IsTrue(newCity.CanAssignCityZoneIfAuthorized);
                Assert.IsTrue(newCity.CanAutoAssignCityZone);

                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);

                Assert.IsFalse(newCity.CanAssignCityZoneIfAuthorized);
                Assert.IsFalse(newCity.CanAutoAssignCityZone);

                Assert.AreEqual(3, so.ChangeSet.ModifiedEntities.Count);
                Assert.AreEqual(1, so.ChangeSet.AddedEntities.Count);
                Assert.AreEqual(2, so.ChangeSet.RemovedEntities.Count);
            });
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Assert.IsNull(so.Error, string.Format("SubmitOperation.Error should be null.\r\nMessage: {0}\r\nStack Trace:\r\n{1}", so.Error != null ? so.Error.Message : string.Empty, so.Error != null ? so.Error.StackTrace : string.Empty));
                Assert.IsTrue(newCity.CanAssignCityZoneIfAuthorized);
                Assert.IsTrue(newCity.CanAutoAssignCityZone);
                Assert.IsTrue(newCity.CanAssignCityZone);

                // verify we got property change notifications for the new city entity (guard property should be reverted once SubmitChanges is called)
                Assert.AreEqual(9, propChanged_addedCity.Count);
                Assert.AreEqual(1, propChanged_addedCity.Count(prop => prop == "ZoneName"));
                Assert.AreEqual(1, propChanged_addedCity.Count(prop => prop == "ZoneID"));
                Assert.AreEqual(1, propChanged_addedCity.Count(prop => prop == "CanAssignCityZone"));
                Assert.AreEqual(1, propChanged_addedCity.Count(prop => prop == "IsAssignCityZoneInvoked"));
                // The other custom method invocations should have changed from true -> false during submit
                // and from false -> true after submit
                Assert.AreEqual(2, propChanged_addedCity.Count(prop => prop == "CanAssignCityZoneIfAuthorized"));
                Assert.AreEqual(2, propChanged_addedCity.Count(prop => prop == "CanAutoAssignCityZone"));


                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                Assert.AreEqual(1, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Zone1" && c.CountyName == "King")).ZoneID);
                Assert.AreEqual(2, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Zone2")).ZoneID);
                Assert.AreEqual(3, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Zone3" && c.Name == newCity.Name)).ZoneID);
                Assert.IsTrue(citiesProvider.Zips.Any<Zip>(z => z.Code == refZipCode - 1));

                // verify unchanged entities
                Assert.IsFalse(citiesProvider.Cities.Any(c => (c.ZoneName == null && c.ZoneID != 0)));

                // verify that after a successful submit, DM invocations on the entities have been accepted
                Assert.IsFalse(so.ChangeSet.ModifiedEntities.Any(p => p.EntityActions.Any()));
                EntityChangeSet changeSet = citiesProvider.EntityContainer.GetChanges();
                Assert.IsTrue(changeSet.IsEmpty);
            });

            EnqueueTestComplete();
        }
        public void Inherit_Run_CUD_Insert_Derived()
        {
            // Inheritance is City <-- CityWithEditHistory <-- CityWithInfo
            CityDomainContext citiesContext= new CityDomainContext(TestURIs.Cities);
            DateTime priorLastUpdated = DateTime.Now;

            // Load all cities, not just derived ones
            LoadOperation lo = citiesContext.Load(citiesContext.GetCitiesQuery());
            SubmitOperation so = null;

            // wait for Load to complete
            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(delegate
            {

                CityWithInfo newCity = new CityWithInfo() { Name = "CocoaVille", StateName = "WA", CountyName = "King", Info="stuff" };
                citiesContext.Cities.Add(newCity);

                so = citiesContext.SubmitChanges();
            });
            // wait for submit to complete
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                if (so.Error != null)
                {
                    Assert.Fail("Unexpected error on submit: " + so.Error.Message);
                }

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                CityWithInfo newCity = citiesContext.Cities.OfType<CityWithInfo>().SingleOrDefault<CityWithInfo>(c => (c.Name == "CocoaVille"));
                Assert.IsNotNull(newCity, "Did not find modified City after the submit");
                Assert.IsTrue(newCity.EditHistory.Contains("insert"), "EditHistory was" + newCity.EditHistory);
            });

            EnqueueTestComplete();
        }
        public void Cities_TestLoad()
        {
            CityDomainContext dp = new CityDomainContext(TestURIs.Cities);    // Abs URI so runs on desktop too

            LoadOperation lo = dp.Load(dp.GetCitiesQuery(), false);

            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(() => 
                {
                    if (lo.Error != null)
                        Assert.Fail("LoadOperation.Error: " + lo.Error.Message);
                    IEnumerable<City> expected = new CityData().Cities;
                    AssertSame(expected, dp.Cities);
                });

            EnqueueTestComplete();
        }
        public void DomainContext_Submit_DomainMethodOnly()
        {
            EntityChangeSet changeset;
            List<string> propChanged = new List<string>();
            CityDomainContext citiesProvider = new CityDomainContext(TestURIs.Cities);

            LoadOperation lo = citiesProvider.Load(citiesProvider.GetCitiesQuery(), false);
            SubmitOperation so = null;

            City firstRootCity = null;
            City lastRootCity = null;

            // wait for Load to complete, then invoke some domain methods
            EnqueueConditional(() => lo.IsComplete);
            EnqueueCallback(delegate
            {
                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.IsTrue(changeset.IsEmpty);

                // Find a root city.  This test specifically does not want to accidently use a derived type
                firstRootCity = citiesProvider.Cities.Where(c => c.GetType() == typeof(City)).FirstOrDefault();
                Assert.IsNotNull(firstRootCity, "Expected to find a root City type in entity list");

                lastRootCity = citiesProvider.Cities.Where(c => c.GetType() == typeof(City)).LastOrDefault();
                Assert.IsNotNull(lastRootCity, "Expected to find a root City type in entity list");

                Assert.AreNotEqual(firstRootCity, lastRootCity, "Expected first and last city to be different");

                firstRootCity.PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
                {
                    bool isEntityBaseProperty = typeof(City).GetProperty(e.PropertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) == null;
                    if (!isEntityBaseProperty)
                    {
                        propChanged.Add(e.PropertyName);
                    }
                };

                Assert.IsTrue(firstRootCity.CanAssignCityZone);
                firstRootCity.AssignCityZone("Zone15");
            });

            // wait for prop changed for domain method guards
            EnqueueConditional(() => propChanged.Count > 0);
            EnqueueCallback(delegate
            {
                Assert.IsTrue(propChanged.Contains("CanAssignCityZone"));
                Assert.IsTrue(propChanged.Contains("IsAssignCityZoneInvoked"));
                Assert.IsFalse(propChanged.Contains("CanAutoAssignCityZone"));
                propChanged.Clear();

                Assert.IsTrue(lastRootCity.CanAutoAssignCityZone);
                lastRootCity.AutoAssignCityZone();

                changeset = citiesProvider.EntityContainer.GetChanges();
                Assert.AreEqual(2, changeset.ModifiedEntities.Count);

                so = citiesProvider.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);

                Assert.AreEqual(2, so.ChangeSet.ModifiedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.AddedEntities.Count);
                Assert.AreEqual(0, so.ChangeSet.RemovedEntities.Count);
            });
            // wait for submit to complete, then verify invoked entities in changeset
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                Assert.IsNull(so.Error);
                Assert.AreEqual(2, so.ChangeSet.ModifiedEntities.Count);

                // verify we got the property change notification for the city entity as a result of autosync
                Assert.AreEqual(8, propChanged.Count);
                Assert.AreEqual(1, propChanged.Count(prop => prop == "ZoneName"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "ZoneID"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "CanAssignCityZone"));
                Assert.AreEqual(1, propChanged.Count(prop => prop == "IsAssignCityZoneInvoked"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanAutoAssignCityZone"));
                Assert.AreEqual(2, propChanged.Count(prop => prop == "CanAssignCityZoneIfAuthorized"));

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                Assert.AreEqual(15, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Zone15")).ZoneID);
                Assert.AreEqual(1, citiesProvider.Cities.Single<City>(c => (c.ZoneName == "Auto_Zone1")).ZoneID);

                // verify unchanged entities
                Assert.AreEqual(0, citiesProvider.Cities.First(c => (c.ZoneName == null)).ZoneID);
            });

            EnqueueTestComplete();
        }
        public void Inherit_Run_CUD_Update_Derived()
        {
            // Inheritance is City <-- CityWithEditHistory <-- CityWithInfo
            CityDomainContext citiesContext = new CityDomainContext(TestURIs.Cities);
            DateTime priorLastUpdated = DateTime.Now;

            // Load all cities, not just derived ones
            LoadOperation lo = citiesContext.Load(citiesContext.GetCitiesQuery());
            SubmitOperation so = null;
            CityWithInfo cityWithInfo = null;
            string originalName = null;
            string originalStateName = null;
            string originalCountyName = null;

            // wait for Load to complete
            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(delegate
            {

                cityWithInfo = citiesContext.Cities.OfType<CityWithInfo>().FirstOrDefault();
                Assert.IsNotNull(cityWithInfo, "expected to find at least one CityWithInfo entity");
                Assert.IsFalse(cityWithInfo.EditHistory.Contains("update"), "Did not expect edit history to be set yet.");

                originalName = cityWithInfo.Name;
                originalStateName = cityWithInfo.StateName;
                originalCountyName = cityWithInfo.CountyName;

                cityWithInfo.Info = "inserted new info";

                so = citiesContext.SubmitChanges();
            });
            // wait for submit to complete
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                if (so.Error != null)
                {
                    Assert.Fail("Unexpected error on submit: " + so.Error.Message);
                }

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                CityWithInfo updatedCity = citiesContext.Cities.OfType<CityWithInfo>().SingleOrDefault<CityWithInfo>
                                                (c => (c.Name == originalName && 
                                                       c.StateName == originalStateName && 
                                                       c.CountyName == originalCountyName));
                Assert.IsNotNull(updatedCity, "Did not find modified City after the submit");
                Assert.IsTrue(updatedCity.EditHistory.Contains("update"), "EditHistory was" + updatedCity.EditHistory);
                Assert.AreEqual("inserted new info", updatedCity.Info, "Updated Info did not get applied");
            });

            EnqueueTestComplete();
        }
        public void CustomMethod_ValidationExceptionRecovery()
        {
            bool completed = false;
            Zip zip = null;
            SubmitOperation submitOp = null;
            LoadOperation<Zip> loadOp = null;
            EventHandler completedDelegate = (sender, args) => completed = true;
            
            CityDomainContext context = new CityDomainContext(TestURIs.Cities);
            loadOp = context.Load(context.GetZipsQuery(), false);
            loadOp.Completed += completedDelegate;

            this.EnqueueConditional(() => completed);
            this.EnqueueCallback(() =>
            {
                Assert.IsFalse(loadOp.HasError, "Failed to load Zips");

                // Grab a Zip and invoke a custom operation that will throw a ValidationException.
                zip = context.Zips.First();
                zip.ThrowException(typeof(ValidationException).Name);

                // Submit
                completed = false;
                submitOp = context.SubmitChanges(TestHelperMethods.DefaultOperationAction, null);
                submitOp.Completed += completedDelegate;

                // Verify entity state
                Assert.IsTrue(zip.IsReadOnly, "Expected Zip to be in a read-only state.");
                Assert.IsTrue(zip.IsSubmitting, "Expected Zip to be in a submitting state.");
                Assert.AreEqual(1, zip.EntityActions.Count(), "Expected Zip EntityActions property to contain a single invocation");
            });
            this.EnqueueConditional(() => completed);
            this.EnqueueCallback(() =>
            {
                // Verify results
                Assert.IsTrue(submitOp.HasError, "Expected errors.");
                Assert.AreEqual(1, submitOp.EntitiesInError.Count(), "Expected 1 Zip entity in error.");
                Assert.AreEqual(submitOp.EntitiesInError.Single(), zip, "Expected 1 Zip entity in error.");
                Assert.AreEqual(1, zip.ValidationErrors.Count, "Expected 1 validation error on Zip entity.");

                // Verify entity state
                Assert.IsFalse(zip.IsReadOnly, "Zip should not be in a read-only state.");
                Assert.IsFalse(zip.IsSubmitting, "Zip should not be in a submitting state.");
                Assert.AreEqual(1, zip.EntityActions.Count(), "Expected Zip EntityActions property to contain a single invocation");

                // Explicitly set a property
                zip.StateName += "x";
            });
            this.EnqueueTestComplete();
        }
        public void Inherit_Run_CUD_Delete_Derived()
        {
            // Inheritance is City <-- CityWithEditHistory <-- CityWithInfo
            CityDomainContext citiesContext = new CityDomainContext(TestURIs.Cities);
            DateTime priorLastUpdated = DateTime.Now;
            LoadOperation lo = null;
            SubmitOperation so = null;
            CityWithInfo cityWithInfo = null;
            string originalName = null;
            string originalStateName = null;
            string originalCountyName = null;

            // Invoke service operation to clear out all static data
            // (we rely on static data for deleted cities so that it
            //  survives across queries)
            InvokeOperation invoke = citiesContext.ResetData(null, null);

            EnqueueConditional(delegate
            {
                return invoke.IsComplete;
            });
            EnqueueCallback(delegate
            {
                if (invoke.Error != null)
                {
                    Assert.Fail("Failed on invoke of ResetData: " + invoke.Error.Message);
                }
            });

            EnqueueCallback(delegate
            {
                // Load all cities, not just derived ones
                lo = citiesContext.Load(citiesContext.GetCitiesQuery());
            });


            // wait for Load to complete
            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(delegate
            {

                cityWithInfo = citiesContext.Cities.OfType<CityWithInfo>().FirstOrDefault();
                Assert.IsNotNull(cityWithInfo, "expected to find at least one CityWithInfo entity");
                Assert.IsFalse(cityWithInfo.EditHistory.Contains("update"), "Did not expect edit history to be set yet.");

                originalName = cityWithInfo.Name;
                originalStateName = cityWithInfo.StateName;
                originalCountyName = cityWithInfo.CountyName;

                // Delete it.  Note that the delete CUD method in the CityDomainService
                // moves the deleted city over into DeletedCities so it can still be found
                citiesContext.Cities.Remove(cityWithInfo);

                so = citiesContext.SubmitChanges();
            });
            // wait for submit to complete
            EnqueueConditional(() => so.IsComplete);
            EnqueueCallback(delegate
            {
                if (so.Error != null)
                {
                    Assert.Fail("Unexpected error on submit: " + so.Error.Message);
                }

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                CityWithInfo deletedCity = citiesContext.Cities.OfType<CityWithInfo>().SingleOrDefault<CityWithInfo>
                                                (c => (c.Name == originalName &&
                                                       c.StateName == originalStateName &&
                                                       c.CountyName == originalCountyName));
                Assert.IsNull(deletedCity, "Did not expect to find deleted City after the submit");
                
                // Load the deleted cities (it was tombstoned)
                citiesContext.Cities.Clear();
                lo = citiesContext.Load(citiesContext.GetDeletedCitiesQuery());
            });

            // Wait for deleted city query to complete
            EnqueueConditional(() => lo.IsComplete);

            EnqueueCallback(delegate
            {
                if (lo.Error != null)
                {
                    Assert.Fail("Unexpected error on load of deleted queries: " + lo.Error.Message);
                }

                // verify entities are auto-synced back to the client as a result of the domain method execution on server
                CityWithInfo deletedCity = citiesContext.Cities.OfType<CityWithInfo>().SingleOrDefault<CityWithInfo>
                                                (c => (c.Name == originalName &&
                                                       c.StateName == originalStateName &&
                                                       c.CountyName == originalCountyName));
                Assert.IsNotNull(deletedCity, "Expected to find deleted City in the tombstone list");
                Assert.IsTrue(deletedCity.EditHistory.Contains("delete"), "Expected edit history to show delete but it only shows: " + deletedCity.EditHistory);
            });

            EnqueueTestComplete();
        }