| /* |
| * Copyright (C) 2023 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.android.adservices.common; |
| |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_ECHO; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_HELP; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_ATTRIBUTION_ACCESS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_TOPICS_ACCESS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_SHORT_HELP; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.ERROR_EMPTY_COMMAND; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.ERROR_TEMPLATE_INVALID_ARGS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_ECHO; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_IS_ALLOWED_ATTRIBUTION_ACCESS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS; |
| import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_IS_ALLOWED_TOPICS_ACCESS; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; |
| |
| import static org.junit.Assert.assertThrows; |
| import static org.mockito.Mockito.mock; |
| |
| import android.content.Context; |
| |
| import com.android.adservices.mockito.AdServicesExtendedMockitoRule; |
| import com.android.adservices.service.common.AppManifestConfigHelper; |
| |
| import com.google.common.truth.Expect; |
| |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.Arrays; |
| |
| public final class AdServicesShellCommandHandlerTest { |
| |
| private static final String PKG_NAME = "d.h.a.r.m.a"; |
| private static final String ENROLLMENT_ID = "42"; |
| private static final String USES_SDK = "true"; |
| |
| @Rule |
| public final AdServicesExtendedMockitoRule extendedMockitoRule = |
| new AdServicesExtendedMockitoRule.Builder(this) |
| .spyStatic(AppManifestConfigHelper.class) |
| .build(); |
| |
| @Rule public final Expect expect = Expect.create(); |
| |
| // mCmd is used on most tests methods, excepted those that runs more than one command |
| private final OneTimeCommand mCmd = new OneTimeCommand(expect); |
| |
| @Test |
| public void testInvalidConstructor() throws Exception { |
| assertThrows( |
| NullPointerException.class, |
| () -> new AdServicesShellCommandHandler(mCmd.context, (PrintWriter) null)); |
| assertThrows( |
| NullPointerException.class, |
| () -> |
| new AdServicesShellCommandHandler( |
| /* context= */ null, mock(PrintWriter.class))); |
| } |
| |
| @Test |
| public void testRun_invalidArgs() throws Exception { |
| assertThrows(NullPointerException.class, () -> mCmd.run(/* args...= */ (String[]) null)); |
| assertThrows(IllegalArgumentException.class, () -> mCmd.run(/* args...= */ new String[0])); |
| } |
| |
| @Test |
| public void testRunHelp() throws Exception { |
| String result = mCmd.runInvalid(CMD_HELP); |
| |
| assertHelpContents(result); |
| } |
| |
| @Test |
| public void testRunHelpShort() throws Exception { |
| String result = mCmd.runInvalid(CMD_SHORT_HELP); |
| |
| assertHelpContents(result); |
| } |
| |
| @Test |
| public void testRun_noCommand() throws Exception { |
| String result = mCmd.runInvalid(""); |
| |
| expect.withMessage("result of ''").that(result).isEqualTo(ERROR_EMPTY_COMMAND + "\n"); |
| } |
| |
| @Test |
| public void testRun_invalidCommand() throws Exception { |
| String cmd = "I can't believe this command is valid"; |
| String result = mCmd.runInvalid(cmd); |
| |
| expect.withMessage("result of '%s'", cmd).that(result).contains("Unknown command: " + cmd); |
| } |
| |
| @Test |
| public void testRunEcho_invalid() throws Exception { |
| // no args |
| expectInvalidArgument(HELP_ECHO, CMD_ECHO); |
| // empty message |
| expectInvalidArgument(HELP_ECHO, CMD_ECHO, ""); |
| // more than 1 arg |
| expectInvalidArgument(HELP_ECHO, CMD_ECHO, "4", "8", "15", "16", "23", "42"); |
| } |
| |
| @Test |
| public void testRunEcho_valid() throws Exception { |
| String result = mCmd.runValid(CMD_ECHO, "108"); |
| |
| expect.withMessage("result of '%s 108'", CMD_ECHO).that(result).isEqualTo("108\n"); |
| } |
| |
| @Test |
| public void testRunIsAllowedAttributionAccess_invalid() throws Exception { |
| // no args |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_ATTRIBUTION_ACCESS, CMD_IS_ALLOWED_ATTRIBUTION_ACCESS); |
| // missing id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_ATTRIBUTION_ACCESS, CMD_IS_ALLOWED_ATTRIBUTION_ACCESS, PKG_NAME); |
| // empty pkg |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_ATTRIBUTION_ACCESS, |
| CMD_IS_ALLOWED_ATTRIBUTION_ACCESS, |
| "", |
| ENROLLMENT_ID); |
| // empty id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_ATTRIBUTION_ACCESS, |
| CMD_IS_ALLOWED_ATTRIBUTION_ACCESS, |
| PKG_NAME, |
| ""); |
| } |
| |
| @Test |
| public void testRunIsAllowedAttributionAccess_valid() throws Exception { |
| doReturn(true) |
| .when( |
| () -> |
| AppManifestConfigHelper.isAllowedAttributionAccess( |
| mCmd.context, PKG_NAME, ENROLLMENT_ID)); |
| |
| expect.withMessage( |
| "result of %s %s %s", |
| CMD_IS_ALLOWED_ATTRIBUTION_ACCESS, PKG_NAME, ENROLLMENT_ID) |
| .that(mCmd.runValid(CMD_IS_ALLOWED_ATTRIBUTION_ACCESS, PKG_NAME, ENROLLMENT_ID)) |
| .isEqualTo("true\n"); |
| } |
| |
| @Test |
| public void testRunIsAllowedCustomAudiencesAccess_invalid() throws Exception { |
| // no args |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS); |
| // missing id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| PKG_NAME); |
| // empty pkg |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| "", |
| ENROLLMENT_ID); |
| // empty id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| PKG_NAME, |
| ""); |
| } |
| |
| @Test |
| public void testRunIsAllowedCustomAudiencesAccess_valid() throws Exception { |
| doReturn(true) |
| .when( |
| () -> |
| AppManifestConfigHelper.isAllowedCustomAudiencesAccess( |
| mCmd.context, PKG_NAME, ENROLLMENT_ID)); |
| |
| expect.withMessage( |
| "result of %s %s %s", |
| CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, PKG_NAME, ENROLLMENT_ID) |
| .that( |
| mCmd.runValid( |
| CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, PKG_NAME, ENROLLMENT_ID)) |
| .isEqualTo("true\n"); |
| } |
| |
| @Test |
| public void testRunIsAllowedTopicsAccess_invalid() throws Exception { |
| // no args |
| expectInvalidArgument(HELP_IS_ALLOWED_TOPICS_ACCESS, CMD_IS_ALLOWED_TOPICS_ACCESS); |
| // missing id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, CMD_IS_ALLOWED_TOPICS_ACCESS, PKG_NAME); |
| // missing sdk |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, |
| CMD_IS_ALLOWED_TOPICS_ACCESS, |
| PKG_NAME, |
| ENROLLMENT_ID); |
| // empty pkg |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, |
| CMD_IS_ALLOWED_TOPICS_ACCESS, |
| "", |
| ENROLLMENT_ID, |
| USES_SDK); |
| // empty id |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, |
| CMD_IS_ALLOWED_TOPICS_ACCESS, |
| PKG_NAME, |
| "", |
| USES_SDK); |
| // empty sdk |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, |
| CMD_IS_ALLOWED_TOPICS_ACCESS, |
| PKG_NAME, |
| ENROLLMENT_ID, |
| ""); |
| // non-boolean sdk |
| expectInvalidArgument( |
| HELP_IS_ALLOWED_TOPICS_ACCESS, |
| CMD_IS_ALLOWED_TOPICS_ACCESS, |
| PKG_NAME, |
| ENROLLMENT_ID, |
| "D'OH!"); |
| } |
| |
| @Test |
| public void testRunIsAllowedTopicsAudiencesAccess_valid() throws Exception { |
| doReturn(true) |
| .when( |
| () -> |
| AppManifestConfigHelper.isAllowedTopicsAccess( |
| mCmd.context, |
| /* useSandboxCheck= */ true, |
| PKG_NAME, |
| ENROLLMENT_ID)); |
| |
| expect.withMessage( |
| "result of %s %s %s %s", |
| CMD_IS_ALLOWED_TOPICS_ACCESS, PKG_NAME, ENROLLMENT_ID, USES_SDK) |
| .that( |
| mCmd.runValid( |
| CMD_IS_ALLOWED_TOPICS_ACCESS, PKG_NAME, ENROLLMENT_ID, USES_SDK)) |
| .isEqualTo("true\n"); |
| } |
| |
| private void assertHelpContents(String help) { |
| expect.withMessage("help") |
| .that(help.split("\n")) |
| .asList() |
| .containsExactly( |
| HELP_ECHO, |
| HELP_IS_ALLOWED_ATTRIBUTION_ACCESS, |
| HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS, |
| HELP_IS_ALLOWED_TOPICS_ACCESS); |
| } |
| |
| private void expectInvalidArgument(String syntax, String... args) throws IOException { |
| OneTimeCommand cmd = new OneTimeCommand(expect); |
| |
| String expectedResult = |
| String.format(ERROR_TEMPLATE_INVALID_ARGS, Arrays.toString(args), syntax) + "\n"; |
| String actualResult = cmd.runInvalid(args); |
| |
| expect.withMessage("result of %s", Arrays.toString(args)) |
| .that(actualResult) |
| .isEqualTo(expectedResult); |
| } |
| |
| /** Helper to run a command and get the output. */ |
| private static final class OneTimeCommand { |
| private final StringWriter mOutStringWriter = new StringWriter(); |
| private final PrintWriter mOut = new PrintWriter(mOutStringWriter); |
| public final Expect expect; |
| |
| public final Context context = mock(Context.class); |
| public final AdServicesShellCommandHandler cmd = |
| new AdServicesShellCommandHandler(context, mOut); |
| |
| private boolean mOutCalled; |
| |
| private OneTimeCommand(Expect expect) { |
| this.expect = expect; |
| } |
| |
| /** |
| * Runs a command that is expected to return a positive result. |
| * |
| * @return command output |
| */ |
| String runValid(String... args) throws IOException { |
| int result = run(args); |
| expect.withMessage("result of run(%s)", Arrays.toString(args)) |
| .that(result) |
| .isAtLeast(0); |
| |
| return getOut(); |
| } |
| |
| /** |
| * Runs a command that is expected to return a negative result. |
| * |
| * @return command output |
| */ |
| String runInvalid(String... args) throws IOException { |
| int result = run(args); |
| expect.withMessage("result of run(%s)", Arrays.toString(args)) |
| .that(result) |
| .isLessThan(0); |
| |
| return getOut(); |
| } |
| |
| /** |
| * Runs the given command. |
| * |
| * @return command result |
| */ |
| int run(String... args) throws IOException { |
| return cmd.run(args); |
| } |
| |
| /** |
| * Returns the output of the command. |
| * |
| * <p>Can only be called once per test, as there is no way to reset it, which could cause |
| * confusion for the test developer. |
| */ |
| String getOut() throws IOException { |
| if (mOutCalled) { |
| throw new IllegalStateException("getOut() already called"); |
| } |
| mOut.flush(); |
| String out = mOutStringWriter.toString(); |
| mOut.close(); |
| mOutCalled = true; |
| return out; |
| } |
| } |
| } |