diff options
5 files changed, 516 insertions, 7 deletions
@@ -21,7 +21,7 @@ limitations under the License. <groupId>gerrit</groupId> <artifactId>gerrit</artifactId> <packaging>war</packaging> - <version>2.0.24.1</version> + <version>2.0.24.2</version> <name>gerrit</name> <description>Gerrit - Web Based Code Review</description> <url>http://android.git.kernel.org/?p=tools/gerrit.git</url> @@ -632,6 +632,13 @@ limitations under the License. </dependency> <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <version>1.2.122</version> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>2.5.1</version> diff --git a/src/main/java/com/google/gerrit/client/reviewdb/AccountGroup.java b/src/main/java/com/google/gerrit/client/reviewdb/AccountGroup.java index 79a35a5dfa..eb38ef212f 100644 --- a/src/main/java/com/google/gerrit/client/reviewdb/AccountGroup.java +++ b/src/main/java/com/google/gerrit/client/reviewdb/AccountGroup.java @@ -105,13 +105,38 @@ public final class AccountGroup { } public static enum Type { - /** System defined and managed group, e.g. anonymous users. */ + /** + * System defined and managed group, e.g. anonymous users. + * <p> + * These groups must be explicitly named by {@link SystemConfig} and are + * specially handled throughout the code. In UI contexts their membership is + * not displayed. When computing effective group membership for any given + * user account, these groups are automatically handled using specialized + * branch conditions. + */ SYSTEM, - /** Group defined within our database. */ + /** + * Group defined within our database. + * <p> + * An internal group has its membership fully enumerated in the database. + * The membership can be viewed and edited through the web UI by any user + * who is a member of the owner group. These groups are not treated special + * in the code. + */ INTERNAL, - /** Group defined by external LDAP database. */ + /** + * Group defined by external LDAP database. + * <p> + * A group whose membership is determined by the LDAP directory that we + * connect to for user and group information. In UI contexts the membership + * of the group is not displayed, as it may be exceedingly large, or might + * contain users who have never logged into this server before (and thus + * have no matching account record). Adding or removing users from an LDAP + * group requires making edits through the LDAP directory, and cannot be + * done through our UI. + */ LDAP; } @@ -151,6 +176,7 @@ public final class AccountGroup { name = newName; groupId = newId; ownerGroupId = groupId; + setType(Type.INTERNAL); } public AccountGroup.Id getId() { diff --git a/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java b/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java index 15c9ecb56b..ab5a2933f0 100644 --- a/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java +++ b/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java @@ -38,13 +38,13 @@ import java.util.Collections; import java.util.List; /** Loads the {@link SystemConfig} from the database. */ -class SystemConfigProvider implements Provider<SystemConfig> { +public class SystemConfigProvider implements Provider<SystemConfig> { private static final Project.NameKey DEFAULT_WILD_NAME = new Project.NameKey("-- All Projects --"); private final SchemaFactory<ReviewDb> schema; @Inject - SystemConfigProvider(final SchemaFactory<ReviewDb> sf) { + public SystemConfigProvider(final SchemaFactory<ReviewDb> sf) { schema = sf; } @@ -133,6 +133,7 @@ class SystemConfigProvider implements Provider<SystemConfig> { new AccountGroup(new AccountGroup.NameKey("Administrators"), new AccountGroup.Id(c.nextAccountGroupId())); admin.setDescription("Gerrit Site Administrators"); + admin.setType(AccountGroup.Type.INTERNAL); c.accountGroups().insert(Collections.singleton(admin)); final AccountGroup anonymous = @@ -148,7 +149,7 @@ class SystemConfigProvider implements Provider<SystemConfig> { new AccountGroup.Id(c.nextAccountGroupId())); registered.setDescription("Any signed-in user"); registered.setOwnerGroupId(admin.getId()); - anonymous.setType(AccountGroup.Type.SYSTEM); + registered.setType(AccountGroup.Type.SYSTEM); c.accountGroups().insert(Collections.singleton(registered)); File sitePath = new File(".").getAbsoluteFile(); @@ -255,6 +256,14 @@ class SystemConfigProvider implements Provider<SystemConfig> { final ProjectRight read = new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), sConfig.anonymousGroupId)); + read.setMaxValue((short) 1); + read.setMinValue((short) 1); + c.projectRights().insert(Collections.singleton(read)); + } + { + final ProjectRight read = + new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), + sConfig.registeredGroupId)); read.setMaxValue((short) 2); read.setMinValue((short) 1); c.projectRights().insert(Collections.singleton(read)); diff --git a/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java b/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java new file mode 100644 index 0000000000..c1fce7b042 --- /dev/null +++ b/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java @@ -0,0 +1,362 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.config; + +import com.google.gerrit.client.reviewdb.AccountGroup; +import com.google.gerrit.client.reviewdb.ApprovalCategory; +import com.google.gerrit.client.reviewdb.ApprovalCategoryValue; +import com.google.gerrit.client.reviewdb.Project; +import com.google.gerrit.client.reviewdb.ProjectRight; +import com.google.gerrit.client.reviewdb.ReviewDb; +import com.google.gerrit.client.reviewdb.SchemaVersion; +import com.google.gerrit.client.reviewdb.SystemConfig; +import com.google.gerrit.server.workflow.NoOpFunction; +import com.google.gerrit.server.workflow.SubmitFunction; +import com.google.gerrit.testutil.TestDatabase; +import com.google.gwtorm.client.OrmException; + +import junit.framework.TestCase; + +import java.io.File; +import java.sql.SQLException; +import java.util.HashSet; + +public class SystemConfigProviderTest extends TestCase { + private ApprovalCategory.Id codeReview = new ApprovalCategory.Id("CRVW"); + private TestDatabase db; + + @Override + protected void setUp() throws Exception { + super.setUp(); + db = new TestDatabase(); + } + + @Override + protected void tearDown() throws Exception { + TestDatabase.drop(db); + super.tearDown(); + } + + public void testGetCauses_CreateSchema() throws OrmException { + // Initially the schema should be empty. + // + try { + getSchemaVersion(); + fail("Brand new test database has schema_version table"); + } catch (OrmException e) { + final Throwable cause = e.getCause(); + assertTrue(cause instanceof SQLException); + + final String msg = cause.getMessage(); + assertEquals("Table SCHEMA_VERSION not found", msg.split(";")[0]); + } + + // Create the schema using the current schema version. + // + final SystemConfig config = getSystemConfig(); + final SchemaVersion version = getSchemaVersion(); + assertNotNull(version); + assertEquals(ReviewDb.VERSION, version.versionNbr); + + assertNotNull(config); + assertNotNull(config.adminGroupId); + assertNotNull(config.anonymousGroupId); + assertNotNull(config.registeredGroupId); + + // By default sitePath is set to the current working directory. + // + File sitePath = new File(".").getAbsoluteFile(); + if (sitePath.getName().equals(".")) { + sitePath = sitePath.getParentFile(); + } + assertEquals(sitePath.getAbsolutePath(), config.sitePath); + + // This is randomly generated and should be at least 20 bytes long. + // + assertNotNull(config.registerEmailPrivateKey); + assertTrue(20 < config.registerEmailPrivateKey.length()); + } + + public void testSubsequentGetReads() { + final SystemConfig exp = getSystemConfig(); + final SystemConfig act = getSystemConfig(); + + assertNotSame(exp, act); + assertEquals(exp.adminGroupId, act.adminGroupId); + assertEquals(exp.anonymousGroupId, act.anonymousGroupId); + assertEquals(exp.registeredGroupId, act.registeredGroupId); + assertEquals(exp.sitePath, act.sitePath); + assertEquals(exp.registerEmailPrivateKey, act.registerEmailPrivateKey); + } + + public void testCreateSchema_Group_Administrators() throws OrmException { + final SystemConfig config = getSystemConfig(); + final ReviewDb c = db.open(); + try { + final AccountGroup admin = c.accountGroups().get(config.adminGroupId); + assertNotNull(admin); + assertEquals(config.adminGroupId, admin.getId()); + assertEquals("Administrators", admin.getName()); + assertSame(AccountGroup.Type.INTERNAL, admin.getType()); + } finally { + c.close(); + } + } + + public void testCreateSchema_Group_AnonymousUsers() throws OrmException { + final SystemConfig config = getSystemConfig(); + final ReviewDb c = db.open(); + try { + final AccountGroup anon = c.accountGroups().get(config.anonymousGroupId); + assertNotNull(anon); + assertEquals(config.anonymousGroupId, anon.getId()); + assertEquals("Anonymous Users", anon.getName()); + assertSame(AccountGroup.Type.SYSTEM, anon.getType()); + } finally { + c.close(); + } + } + + public void testCreateSchema_Group_RegisteredUsers() throws OrmException { + final SystemConfig config = getSystemConfig(); + final ReviewDb c = db.open(); + try { + final AccountGroup reg = c.accountGroups().get(config.registeredGroupId); + assertNotNull(reg); + assertEquals(config.registeredGroupId, reg.getId()); + assertEquals("Registered Users", reg.getName()); + assertSame(AccountGroup.Type.SYSTEM, reg.getType()); + } finally { + c.close(); + } + } + + public void testCreateSchema_WildCardProject() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final Project all; + + all = c.projects().get(WildProjectNameProvider.WILD_PROJECT_ID); + assertNotNull(all); + assertEquals("-- All Projects --", all.getName()); + assertEquals(new Project.Id(0), all.getId()); + assertFalse(all.isUseContributorAgreements()); + assertFalse(all.isUseSignedOffBy()); + } finally { + c.close(); + } + } + + public void testCreateSchema_ApprovalCategory_CodeReview() + throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(codeReview); + assertNotNull(cat); + assertEquals(codeReview, cat.getId()); + assertEquals("Code Review", cat.getName()); + assertEquals("R", cat.getAbbreviatedName()); + assertEquals("MaxWithBlock", cat.getFunctionName()); + assertTrue(cat.isCopyMinScore()); + assertFalse(cat.isAction()); + assertTrue(0 <= cat.getPosition()); + } finally { + c.close(); + } + assertValueRange(codeReview, -2, -1, 0, 1, 2); + } + + public void testCreateSchema_ApprovalCategory_Read() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(ApprovalCategory.READ); + assertNotNull(cat); + assertEquals(ApprovalCategory.READ, cat.getId()); + assertEquals("Read Access", cat.getName()); + assertNull(cat.getAbbreviatedName()); + assertEquals(NoOpFunction.NAME, cat.getFunctionName()); + assertTrue(cat.isAction()); + } finally { + c.close(); + } + assertValueRange(ApprovalCategory.READ, -1, 1, 2); + } + + public void testCreateSchema_ApprovalCategory_Submit() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(ApprovalCategory.SUBMIT); + assertNotNull(cat); + assertEquals(ApprovalCategory.SUBMIT, cat.getId()); + assertEquals("Submit", cat.getName()); + assertNull(cat.getAbbreviatedName()); + assertEquals(SubmitFunction.NAME, cat.getFunctionName()); + assertTrue(cat.isAction()); + } finally { + c.close(); + } + assertValueRange(ApprovalCategory.SUBMIT, 1); + } + + public void testCreateSchema_ApprovalCategory_PushTag() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(ApprovalCategory.PUSH_TAG); + assertNotNull(cat); + assertEquals(ApprovalCategory.PUSH_TAG, cat.getId()); + assertEquals("Push Annotated Tag", cat.getName()); + assertNull(cat.getAbbreviatedName()); + assertEquals(NoOpFunction.NAME, cat.getFunctionName()); + assertTrue(cat.isAction()); + } finally { + c.close(); + } + assertValueRange(ApprovalCategory.PUSH_TAG, // + ApprovalCategory.PUSH_TAG_SIGNED, // + ApprovalCategory.PUSH_TAG_ANNOTATED, // + ApprovalCategory.PUSH_TAG_ANY); + } + + public void testCreateSchema_ApprovalCategory_PushHead() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(ApprovalCategory.PUSH_HEAD); + assertNotNull(cat); + assertEquals(ApprovalCategory.PUSH_HEAD, cat.getId()); + assertEquals("Push Branch", cat.getName()); + assertNull(cat.getAbbreviatedName()); + assertEquals(NoOpFunction.NAME, cat.getFunctionName()); + assertTrue(cat.isAction()); + } finally { + c.close(); + } + assertValueRange(ApprovalCategory.PUSH_HEAD, // + ApprovalCategory.PUSH_HEAD_UPDATE, // + ApprovalCategory.PUSH_HEAD_CREATE, // + ApprovalCategory.PUSH_HEAD_REPLACE); + } + + public void testCreateSchema_ApprovalCategory_Owner() throws OrmException { + final ReviewDb c = db.create().open(); + try { + final ApprovalCategory cat; + + cat = c.approvalCategories().get(ApprovalCategory.OWN); + assertNotNull(cat); + assertEquals(ApprovalCategory.OWN, cat.getId()); + assertEquals("Owner", cat.getName()); + assertNull(cat.getAbbreviatedName()); + assertEquals(NoOpFunction.NAME, cat.getFunctionName()); + assertTrue(cat.isAction()); + } finally { + c.close(); + } + assertValueRange(ApprovalCategory.OWN, 1); + } + + private void assertValueRange(ApprovalCategory.Id cat, int... range) + throws OrmException { + final HashSet<ApprovalCategoryValue.Id> act = + new HashSet<ApprovalCategoryValue.Id>(); + final ReviewDb c = db.open(); + try { + for (ApprovalCategoryValue v : c.approvalCategoryValues().byCategory(cat)) { + assertNotNull(v.getId()); + assertNotNull(v.getName()); + assertEquals(cat, v.getCategoryId()); + assertFalse(v.getName().isEmpty()); + + act.add(v.getId()); + } + } finally { + c.close(); + } + + for (int value : range) { + final ApprovalCategoryValue.Id exp = + new ApprovalCategoryValue.Id(cat, (short) value); + if (!act.remove(exp)) { + fail("Category " + cat + " lacks value " + value); + } + } + if (!act.isEmpty()) { + fail("Category " + cat + " has additional values: " + act); + } + } + + public void testCreateSchema_DefaultAccess_AnonymousUsers() + throws OrmException { + final SystemConfig config = getSystemConfig(); + assertDefaultRight(config.anonymousGroupId, ApprovalCategory.READ, 1, 1); + } + + public void testCreateSchema_DefaultAccess_RegisteredUsers() + throws OrmException { + final SystemConfig config = getSystemConfig(); + assertDefaultRight(config.registeredGroupId, ApprovalCategory.READ, 1, 2); + assertDefaultRight(config.registeredGroupId, codeReview, -1, 1); + } + + public void testCreateSchema_DefaultAccess_Administrators() + throws OrmException { + final SystemConfig config = getSystemConfig(); + assertDefaultRight(config.adminGroupId, ApprovalCategory.READ, 1, 1); + } + + private void assertDefaultRight(final AccountGroup.Id group, + final ApprovalCategory.Id category, int min, int max) throws OrmException { + final ReviewDb c = db.open(); + try { + final Project all; + final ProjectRight right; + + all = c.projects().get(WildProjectNameProvider.WILD_PROJECT_ID); + right = c.projectRights().get( // + new ProjectRight.Key(all.getNameKey(), category, group)); + + assertNotNull(right); + assertEquals(all.getNameKey(), right.getProjectNameKey()); + assertEquals(group, right.getAccountGroupId()); + assertEquals(category, right.getApprovalCategoryId()); + assertEquals(min, right.getMinValue()); + assertEquals(max, right.getMaxValue()); + } finally { + c.close(); + } + } + + private SystemConfig getSystemConfig() { + return new SystemConfigProvider(db).get(); + } + + private SchemaVersion getSchemaVersion() throws OrmException { + final ReviewDb c = db.open(); + try { + return c.schemaVersion().get(new SchemaVersion.Key()); + } finally { + c.close(); + } + } +} diff --git a/src/test/java/com/google/gerrit/testutil/TestDatabase.java b/src/test/java/com/google/gerrit/testutil/TestDatabase.java new file mode 100644 index 0000000000..d628403c4f --- /dev/null +++ b/src/test/java/com/google/gerrit/testutil/TestDatabase.java @@ -0,0 +1,105 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.testutil; + +import com.google.gerrit.client.reviewdb.ReviewDb; +import com.google.gerrit.server.config.SystemConfigProvider; +import com.google.gwtorm.client.OrmException; +import com.google.gwtorm.client.SchemaFactory; +import com.google.gwtorm.jdbc.Database; +import com.google.gwtorm.jdbc.SimpleDataSource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +/** + * An in-memory test instance of {@link ReviewDb} database. + * <p> + * Test classes should create one instance of this class for each unique test + * database they want to use. When the tests needing this instance are complete, + * ensure that {@link #drop(TestDatabase)} is called to free the resources so + * the JVM running the unit tests doesn't run out of heap space. + */ +public class TestDatabase implements SchemaFactory<ReviewDb> { + private static int dbCnt; + + private static synchronized DataSource newDataSource() throws SQLException { + final Properties p = new Properties(); + p.setProperty("driver", org.h2.Driver.class.getName()); + p.setProperty("url", "jdbc:h2:mem:" + "Test_" + (++dbCnt)); + final DataSource dataSource = new SimpleDataSource(p); + return dataSource; + } + + /** Drop the database from memory; does nothing if the instance was null. */ + public static void drop(final TestDatabase db) { + if (db != null) { + db.drop(); + } + } + + private Connection openHandle; + private Database<ReviewDb> database; + + public TestDatabase() throws OrmException { + try { + final DataSource dataSource = newDataSource(); + + // Open one connection. This will peg the database into memory + // until someone calls drop on us, allowing subsequent connections + // opened against the same URL to go to the same set of tables. + // + openHandle = dataSource.getConnection(); + + // Build the access layer around the connection factory. + // + database = new Database<ReviewDb>(dataSource, ReviewDb.class); + } catch (SQLException e) { + throw new OrmException(e); + } + } + + public Database<ReviewDb> getDatabase() { + return database; + } + + @Override + public ReviewDb open() throws OrmException { + return getDatabase().open(); + } + + /** Ensure the database schema has been created and initialized. */ + public TestDatabase create() { + new SystemConfigProvider(this).get(); + return this; + } + + /** Drop this database from memory so it no longer exists. */ + public void drop() { + if (openHandle != null) { + try { + openHandle.close(); + } catch (SQLException e) { + System.err.println("WARNING: Cannot close database connection"); + e.printStackTrace(System.err); + } + openHandle = null; + database = null; + } + } +} |