// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/policy/core/common/schema_registry.h" #include #include "base/test/gtest_util.h" #include "components/policy/core/common/policy_namespace.h" #include "components/policy/core/common/schema.h" #include "extensions/buildflags/buildflags.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::Mock; using ::testing::_; namespace policy { namespace { const char kTestSchema[] = R"({ "type": "object", "properties": { "string": { "type": "string" }, "integer": { "type": "integer" }, "boolean": { "type": "boolean" }, "double": { "type": "number" }, "list": { "type": "array", "items": { "type": "string" } }, "object": { "type": "object", "properties": { "a": { "type": "string" }, "b": { "type": "integer" } } } } })"; class MockSchemaRegistryObserver : public SchemaRegistry::Observer { public: MockSchemaRegistryObserver() {} ~MockSchemaRegistryObserver() override {} MOCK_METHOD1(OnSchemaRegistryUpdated, void(bool)); MOCK_METHOD0(OnSchemaRegistryReady, void()); }; bool SchemaMapEquals(const scoped_refptr& schema_map1, const scoped_refptr& schema_map2) { PolicyNamespaceList added; PolicyNamespaceList removed; schema_map1->GetChanges(schema_map2, &removed, &added); return added.empty() && removed.empty(); } } // namespace TEST(SchemaRegistryTest, Notifications) { std::string error; Schema schema = Schema::Parse(kTestSchema, &error); ASSERT_TRUE(schema.valid()) << error; MockSchemaRegistryObserver observer; SchemaRegistry registry; registry.AddObserver(&observer); ASSERT_TRUE(registry.schema_map().get()); EXPECT_FALSE(registry.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), schema); Mock::VerifyAndClearExpectations(&observer); // Re-register also triggers notifications, because the Schema might have // changed. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), schema); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(registry.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry.UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")); Mock::VerifyAndClearExpectations(&observer); EXPECT_FALSE(registry.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); // Registering multiple components at once issues only one notification. ComponentMap components; components["abc"] = schema; components["def"] = schema; components["xyz"] = schema; EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry.RegisterComponents(POLICY_DOMAIN_EXTENSIONS, components); Mock::VerifyAndClearExpectations(&observer); registry.RemoveObserver(&observer); } TEST(SchemaRegistryTest, IsReady) { SchemaRegistry registry; MockSchemaRegistryObserver observer; registry.AddObserver(&observer); EXPECT_FALSE(registry.IsReady()); #if BUILDFLAG(ENABLE_EXTENSIONS) EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0); registry.SetExtensionsDomainsReady(); Mock::VerifyAndClearExpectations(&observer); EXPECT_FALSE(registry.IsReady()); #endif EXPECT_CALL(observer, OnSchemaRegistryReady()); registry.SetDomainReady(POLICY_DOMAIN_CHROME); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(registry.IsReady()); EXPECT_CALL(observer, OnSchemaRegistryReady()).Times(0); registry.SetDomainReady(POLICY_DOMAIN_CHROME); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(registry.IsReady()); CombinedSchemaRegistry combined; EXPECT_TRUE(combined.IsReady()); registry.RemoveObserver(&observer); } TEST(SchemaRegistryTest, Combined) { std::string error; Schema schema = Schema::Parse(kTestSchema, &error); ASSERT_TRUE(schema.valid()) << error; MockSchemaRegistryObserver observer; std::unique_ptr registry1(new SchemaRegistry); std::unique_ptr registry2(new SchemaRegistry); CombinedSchemaRegistry combined; combined.AddObserver(&observer); EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0); registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), schema); Mock::VerifyAndClearExpectations(&observer); // Starting to track a registry issues notifications when it comes with new // schemas. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); combined.Track(registry1.get()); Mock::VerifyAndClearExpectations(&observer); // Adding a new empty registry does not trigger notifications. EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0); combined.Track(registry2.get()); Mock::VerifyAndClearExpectations(&observer); // Adding the same component to the combined registry itself triggers // notifications. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); combined.RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), schema); Mock::VerifyAndClearExpectations(&observer); // Adding components to the sub-registries triggers notifications. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"), schema); Mock::VerifyAndClearExpectations(&observer); // If the same component is published in 2 sub-registries then the combined // registry publishes one of them. EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"), schema); Mock::VerifyAndClearExpectations(&observer); ASSERT_EQ(1u, combined.schema_map()->GetDomains().size()); ASSERT_TRUE(combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS)); ASSERT_EQ( 2u, combined.schema_map()->GetComponents(POLICY_DOMAIN_EXTENSIONS)->size()); EXPECT_TRUE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); EXPECT_TRUE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"))); EXPECT_FALSE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "xyz"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry1->UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")); Mock::VerifyAndClearExpectations(&observer); // Still registered at the combined registry. EXPECT_TRUE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); combined.UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")); Mock::VerifyAndClearExpectations(&observer); // Now it's gone. EXPECT_FALSE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry1->UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")); Mock::VerifyAndClearExpectations(&observer); // Still registered at registry2. EXPECT_TRUE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry2->UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def")); Mock::VerifyAndClearExpectations(&observer); // Now it's gone. EXPECT_FALSE(combined.schema_map()->GetSchema( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "def"))); EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)).Times(2); registry1->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""), schema); registry2->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "hij"), schema); Mock::VerifyAndClearExpectations(&observer); // Untracking |registry1| doesn't trigger an update notification, because it // doesn't contain any components. EXPECT_CALL(observer, OnSchemaRegistryUpdated(_)).Times(0); registry1.reset(); Mock::VerifyAndClearExpectations(&observer); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry2.reset(); Mock::VerifyAndClearExpectations(&observer); combined.RemoveObserver(&observer); } TEST(SchemaRegistryTest, ForwardingSchemaRegistry) { std::unique_ptr registry(new SchemaRegistry); ForwardingSchemaRegistry forwarding(registry.get()); MockSchemaRegistryObserver observer; forwarding.AddObserver(&observer); EXPECT_FALSE(registry->IsReady()); EXPECT_FALSE(forwarding.IsReady()); // They always have the same SchemaMap. EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map())); EXPECT_CALL(observer, OnSchemaRegistryUpdated(true)); registry->RegisterComponent(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc"), Schema()); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map())); EXPECT_CALL(observer, OnSchemaRegistryUpdated(false)); registry->UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, "abc")); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map())); // No notifications expected for these calls. EXPECT_FALSE(registry->IsReady()); EXPECT_FALSE(forwarding.IsReady()); registry->SetExtensionsDomainsReady(); EXPECT_FALSE(registry->IsReady()); EXPECT_FALSE(forwarding.IsReady()); EXPECT_CALL(observer, OnSchemaRegistryReady()); registry->SetDomainReady(POLICY_DOMAIN_CHROME); EXPECT_TRUE(registry->IsReady()); EXPECT_TRUE(forwarding.IsReady()); Mock::VerifyAndClearExpectations(&observer); EXPECT_TRUE(SchemaMapEquals(registry->schema_map(), forwarding.schema_map())); Mock::VerifyAndClearExpectations(&observer); forwarding.SetExtensionsDomainsReady(); forwarding.SetDomainReady(POLICY_DOMAIN_CHROME); EXPECT_TRUE(forwarding.IsReady()); // Keep the same SchemaMap when the original registry is gone. // No notifications are expected in this case either. scoped_refptr schema_map = registry->schema_map(); registry.reset(); EXPECT_TRUE(SchemaMapEquals(schema_map, forwarding.schema_map())); Mock::VerifyAndClearExpectations(&observer); forwarding.RemoveObserver(&observer); } TEST(SchemaRegistryTest, ForwardingSchemaRegistryReadiness) { std::unique_ptr registry(new SchemaRegistry); ForwardingSchemaRegistry forwarding_1(registry.get()); EXPECT_FALSE(registry->IsReady()); EXPECT_FALSE(forwarding_1.IsReady()); // Once the wrapped registry gets ready, the forwarding schema registry // becomes ready too. registry->SetAllDomainsReady(); EXPECT_TRUE(registry->IsReady()); EXPECT_TRUE(forwarding_1.IsReady()); // The wrapped registry was ready at the time when the forwarding registry was // constructed, so the forwarding registry is immediately ready too. ForwardingSchemaRegistry forwarding_2(registry.get()); EXPECT_TRUE(forwarding_2.IsReady()); // Destruction of the wrapped registry doesn't change the readiness of the // forwarding registry. registry.reset(); EXPECT_TRUE(forwarding_1.IsReady()); EXPECT_TRUE(forwarding_2.IsReady()); } // Extension policy unregister before register shouldn't cause DCHECK failure. // However, Chrome policy should always register first. TEST(SchemaRegistryTest, UnregisterBeforeRegister) { SchemaRegistry registry; ASSERT_NO_FATAL_FAILURE(registry.UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_EXTENSIONS, ""))); ASSERT_NO_FATAL_FAILURE(registry.UnregisterComponent( PolicyNamespace(POLICY_DOMAIN_SIGNIN_EXTENSIONS, ""))); ASSERT_DCHECK_DEATH( registry.UnregisterComponent(PolicyNamespace(POLICY_DOMAIN_CHROME, ""))); } } // namespace policy