/*
 * Copyright (C) 2015 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 androidx.test.espresso.intent.matcher;

import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry;
import static androidx.test.espresso.intent.matcher.IntentMatchers.doesNotHaveExtraWithKey;
import static androidx.test.espresso.intent.matcher.IntentMatchers.filterEquals;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasCategories;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtras;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasFlags;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasPackage;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasType;
import static androidx.test.espresso.intent.matcher.IntentMatchers.isInternal;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.intent.matcher.UriMatchers.hasHost;
import static androidx.test.espresso.intent.matcher.UriMatchers.hasParamWithValue;
import static androidx.test.espresso.intent.matcher.UriMatchers.hasPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.rules.ExpectedException.none;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.test.espresso.intent.ResolvedIntent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import java.util.HashSet;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

/** Unit tests for {@link androidx.test.espresso.intent.matcher.IntentMatchers}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class IntentMatchersTest {

  @Rule public ExpectedException expectedException = none();

  private final Uri uri = Uri.parse("https://www.google.com/search?q=Matcher");

  @Test
  public void matchesTesting() {
    Matcher<Intent> matcher =
        allOf(
            hasAction(equalTo(Intent.ACTION_VIEW)),
            hasData(
                allOf(
                    hasHost(equalTo("www.google.com")),
                    hasPath(equalTo("/search")),
                    hasParamWithValue(equalTo("q"), equalTo("Matcher")))),
            hasCategories(hasItem(equalTo("category"))),
            hasType(equalTo(Context.ACTIVITY_SERVICE)),
            hasExtras(hasEntry(equalTo("key"), equalTo("value"))));
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE)
            .putExtra("key", "value");
    assertTrue(matcher.matches(intent));
  }

  @Test
  public void matchesIntentWithNoTypeAndMatcherWithType() {
    Matcher<Intent> matcher =
        allOf(
            hasAction(equalTo(Intent.ACTION_VIEW)),
            hasData(
                allOf(
                    hasHost(equalTo("www.google.com")),
                    hasPath(equalTo("/search")),
                    hasParamWithValue(equalTo("q"), equalTo("Matcher")))),
            hasCategories(hasItem(equalTo("category"))),
            hasType(equalTo(Context.ACTIVITY_SERVICE)),
            hasExtras(hasEntry(equalTo("key"), equalTo("value"))));
    assertFalse(
        matcher.matches(new Intent(Intent.ACTION_VIEW).setData(uri).addCategory("category")));
  }

  @Test
  public void matchesIntentWithTypeAndMatcherWithNoType() {
    Matcher<Intent> matcher = hasAction(equalTo(Intent.ACTION_VIEW));
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE);
    assertTrue(matcher.matches(intent));
  }

  @Test
  public void matchesIntentWithNoExtraAndMatcherWithOneExtra() {
    Matcher<Intent> matcher =
        allOf(
            hasAction(equalTo(Intent.ACTION_VIEW)),
            hasData(
                allOf(
                    hasHost(equalTo("www.google.com")),
                    hasPath(equalTo("/search")),
                    hasParamWithValue(equalTo("q"), equalTo("Matcher")))),
            hasCategories(hasItem(equalTo("category"))),
            hasType(equalTo(Context.ACTIVITY_SERVICE)),
            hasExtras(hasEntry(equalTo("key"), equalTo("value"))));
    assertFalse(
        matcher.matches(
            new Intent(Intent.ACTION_VIEW)
                .addCategory("category")
                .setDataAndType(uri, Context.ACTIVITY_SERVICE)));
  }

  @Test
  public void matchesIntentWithOneExtraAndMatcherWithNoExtra() {
    Matcher<Intent> matcher =
        allOf(
            hasAction(equalTo(Intent.ACTION_VIEW)),
            hasData(
                allOf(
                    hasHost(equalTo("www.google.com")),
                    hasPath(equalTo("/search")),
                    hasParamWithValue(equalTo("q"), equalTo("Matcher")))),
            hasCategories(hasItem(equalTo("category"))),
            hasType(equalTo(Context.ACTIVITY_SERVICE)));
    assertTrue(
        matcher.matches(
            new Intent(Intent.ACTION_VIEW)
                .addCategory("category")
                .setDataAndType(uri, Context.ACTIVITY_SERVICE)
                .putExtra("key", "value")));
  }

  @Test
  public void matchesIntentWithMultipleExtraAndMatcherWithMultipleExtra() {
    Matcher<Intent> matcher =
        hasExtras(
            allOf(
                hasEntry(equalTo("key1"), equalTo("value1")),
                hasEntry(equalTo("key2"), equalTo("value2"))));
    assertTrue(
        matcher.matches(
            new Intent(Intent.ACTION_VIEW)
                .addCategory("category")
                .setDataAndType(uri, Context.ACTIVITY_SERVICE)
                .putExtra("key1", "value1")
                .putExtra("key2", "value2")));
  }

  @Test
  public void matchesIntentWithNoCategoryAndMatcherWithOneCategory() {
    Matcher<Intent> matcher = hasCategories(hasItem(equalTo("category")));
    assertFalse(matcher.matches(new Intent(Intent.ACTION_VIEW, uri)));
  }

  @Test
  public void matchesIntentWithOneCategoryAndMatcherWithNoCategory() {
    Matcher<Intent> matcher = hasAction(equalTo(Intent.ACTION_VIEW));
    assertTrue(matcher.matches(new Intent(Intent.ACTION_VIEW, uri).addCategory("category")));
  }

  @Test
  public void matchesIntentAndMatcherWithMultipleCategory() {
    Matcher<Intent> matcher =
        hasCategories(allOf(hasItem(equalTo("category")), hasItem(equalTo("category1"))));
    assertTrue(
        matcher.matches(
            new Intent(Intent.ACTION_VIEW, uri).addCategory("category").addCategory("category1")));
  }

  @Test
  public void matchesIntentWithOneCategoryAndMatcherWithMultipleCategories() {
    Matcher<Intent> matcher =
        hasCategories(
            allOf(
                hasItem(equalTo("category")),
                hasItem(equalTo("category1")),
                hasItem(equalTo("category2"))));
    assertFalse(matcher.matches(new Intent(Intent.ACTION_VIEW, uri).addCategory("category")));
  }

  @Test
  public void hasActionTesting() {
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE)
            .putExtra("key", "value");
    assertTrue(hasAction(Intent.ACTION_VIEW).matches(intent));
    assertTrue(hasAction(equalTo(Intent.ACTION_VIEW)).matches(intent));
  }

  @Test
  public void hasActionDoesNotMatch() {
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE)
            .putExtra("key", "value");
    assertFalse(hasAction(Intent.ACTION_DIAL).matches(intent));
    assertFalse(hasAction(equalTo(Intent.ACTION_DIAL)).matches(intent));
  }

  @Test
  public void hasCategoriesTesting() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addCategory("category");
    intent.addCategory("category1");
    intent.addCategory("category2");

    assertTrue(hasCategories(intent.getCategories()).matches(intent));
    assertTrue(hasCategories(hasItems("category", "category1", "category2")).matches(intent));
  }

  @Test
  public void hasCategoriesDoesNotMatch() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addCategory("category");
    intent.addCategory("category1");
    intent.addCategory("category2");

    HashSet<String> set = new HashSet<String>();
    set.add("category");
    set.add("category1");
    set.add("category8");

    assertFalse(hasCategories(set).matches(intent));
    assertFalse(hasCategories(hasItems("category", "category1", "category8")).matches(intent));
  }

  @Test
  public void hasComponentTesting() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    String pkg = "com.some.wonderful.package.name";
    String cls = pkg + ".FooBar";
    ComponentName c = new ComponentName(pkg, cls);
    intent.setComponent(c);

    assertTrue(hasComponent(intent.getComponent().getClassName()).matches(intent));
    assertTrue(hasComponent(intent.getComponent()).matches(intent));
    assertTrue(hasComponent(equalTo(c)).matches(intent));
  }

  @Test
  public void hasComponentDoesNotMatch() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    String pkg = "com.some.wonderful.package.name";
    String cls = pkg + ".FooBar";
    ComponentName c = new ComponentName(pkg, cls);
    intent.setComponent(c);

    c = new ComponentName(pkg, pkg + ".Baz");
    assertFalse(hasComponent("not_component").matches(intent));
    assertFalse(hasComponent(c).matches(intent));
    assertFalse(hasComponent(equalTo(c)).matches(intent));
  }

  @Test
  public void hasDataTesting() {
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE)
            .putExtra("key1", "value1");

    assertTrue(hasData(uri.toString()).matches(intent));
    assertTrue(hasData(uri).matches(intent));
    assertTrue(hasData(equalTo(uri)).matches(intent));
    assertTrue(hasDataString(equalTo(uri.toString())).matches(intent));
  }

  @Test
  public void hasDataDoesMatch() {
    Intent intent =
        new Intent(Intent.ACTION_VIEW)
            .addCategory("category")
            .setDataAndType(uri, Context.ACTIVITY_SERVICE)
            .putExtra("key1", "value1");

    assertFalse(hasData("https://www.google.com/search?q=NotMatcher").matches(intent));
    assertFalse(hasData(Uri.parse("https://www.google.com/search?q=NotMatcher")).matches(intent));
    assertFalse(
        hasData(equalTo(Uri.parse("https://www.google.com/search?q=NotMatcher"))).matches(intent));
    assertFalse(
        hasDataString(equalTo("https://www.google.com/search?q=NotMatcher")).matches(intent));
  }

  @Test
  public void hasTypeTesting() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addCategory("category");
    intent.setDataAndType(uri, Context.ACTIVITY_SERVICE);
    intent.putExtra("key1", "value1");

    assertTrue(hasType(Context.ACTIVITY_SERVICE).matches(intent));
    assertTrue(hasType(equalTo(Context.ACTIVITY_SERVICE)).matches(intent));
  }

  @Test
  public void hasTypeDoesNotMatch() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addCategory("category");
    intent.setDataAndType(uri, Context.ACTIVITY_SERVICE);
    intent.putExtra("key1", "value1");

    assertFalse(hasType(Context.ACCOUNT_SERVICE).matches(intent));
    assertFalse(hasType(equalTo(Context.ACCOUNT_SERVICE)).matches(intent));
  }

  @Test
  public void toPackageTesting() {
    final String pkg = "pkg1";
    ResolvedIntent intent = new FakeResolvedIntent(pkg);
    assertTrue(toPackage(pkg).matches(intent));
    assertFalse(toPackage("notpkg1").matches(intent));
    expectedException.expect(RuntimeException.class);
    toPackage("whatever").matches(new Intent(Intent.ACTION_VIEW));
  }

  @Test
  public void hasExtraWithKeyTesting() {
    Intent intent = new Intent().putExtra("key1", "value1").putExtra("key2", 100.0);
    assertTrue(hasExtraWithKey("key1").matches(intent));
    assertTrue(hasExtraWithKey("key2").matches(intent));
  }

  @Test
  public void doesNotHaveExtraWithKeyTesting() {
    Intent intent = new Intent().putExtra("key1", "value1").putExtra("key2", 100.0);
    assertTrue(doesNotHaveExtraWithKey("key3").matches(intent));
    assertTrue(doesNotHaveExtraWithKey("key4").matches(intent));
  }

  @Test
  public void hasExtraTesting() {
    Intent intent =
        new Intent()
            .putExtra("key1", "value1")
            .putExtra("key2", 100.0)
            .putExtra("key3", (String) null);
    assertTrue(hasExtra("key1", "value1").matches(intent));
    assertTrue(hasExtra("key1", equalTo("value1")).matches(intent));
    assertTrue(hasExtra(equalTo("key1"), equalTo("value1")).matches(intent));
    assertTrue(hasExtra("key2", 100.0).matches(intent));
    assertTrue(hasExtra("key2", equalTo(100.0)).matches(intent));
    assertTrue(hasExtra(equalTo("key2"), equalTo(100.0)).matches(intent));
    assertTrue(hasExtra("key3", null).matches(intent));
    assertTrue(hasExtra("key3", equalTo(null)).matches(intent));
    assertTrue(hasExtra(equalTo("key3"), equalTo(null)).matches(intent));
  }

  @Test
  public void hasExtraWithKeyDoesNotMatch() {
    Intent intent = new Intent().putExtra("key1", "value1");
    assertFalse(hasExtraWithKey("key2").matches(intent));
  }

  @Test
  public void doesNotHaveExtraWithKeyDoesNotMatch() {
    Intent intent = new Intent().putExtra("key1", "value1");
    assertFalse(doesNotHaveExtraWithKey("key1").matches(intent));
    assertFalse(doesNotHaveExtraWithKey("key1").matches(null));
    assertFalse(doesNotHaveExtraWithKey("key1").matches("key1"));
  }

  @Test
  public void hasExtraDoesNotMatch() {
    Intent intent = new Intent().putExtra("key1", "value1");
    assertFalse(hasExtra("key1", "value2").matches(intent));
    assertFalse(hasExtra("key2", "value1").matches(intent));
  }

  @Test
  public void hasPackageMatches() {
    Intent intent = new Intent().setPackage("com.foo.bar");
    assertTrue(hasPackage("com.foo.bar").matches(intent));
    assertTrue(hasPackage(equalTo("com.foo.bar")).matches(intent));
  }

  @Test
  public void hasPackageDoesNotMatch() {
    Intent intent = new Intent().setPackage("com.foo.bar");
    assertFalse(hasPackage("com.baz.qux").matches(intent));
    assertFalse(hasPackage(equalTo("com.baz.qux")).matches(intent));
  }

  @Test
  public void hasPackageNoPackage() {
    Intent intent = new Intent();
    assertFalse(hasPackage("com.foo.bar").matches(intent));
    assertFalse(hasPackage(equalTo("com.foo.bar")).matches(intent));
  }

  @Test
  public void hasFlagsWithSingleFlag() {
    Intent intent = new Intent();
    assertTrue(hasFlags(0).matches(intent));
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    assertTrue(hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).matches(intent));
  }

  @Test
  public void hasFlagsWithMultipleFlags() {
    Intent intent = new Intent();
    intent.setFlags(
        Intent.FLAG_DEBUG_LOG_RESOLUTION
            | Intent.FLAG_ACTIVITY_NO_HISTORY
            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    assertTrue(hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION).matches(intent));
    assertTrue(hasFlags(Intent.FLAG_ACTIVITY_NO_HISTORY).matches(intent));
    assertTrue(hasFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS).matches(intent));
    assertTrue(
        hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
            .matches(intent));
    assertTrue(
        hasFlags(
                Intent.FLAG_DEBUG_LOG_RESOLUTION
                    | Intent.FLAG_ACTIVITY_NO_HISTORY
                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
            .matches(intent));
  }

  @Test
  public void hasFlagsWithCustomFlags() {
    Intent intent = new Intent();
    intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | 8 | 4 | 2);
    assertTrue((hasFlags(8 | 2)).matches(intent));
    assertTrue((hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | 4)).matches(intent));
    assertTrue((hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION, 8, 4)).matches(intent));
  }

  @Test
  public void hasFlagsDoesNotMatch() {
    Intent intent = new Intent();
    assertFalse(hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION).matches(intent));
    intent.setFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | Intent.FLAG_ACTIVITY_NO_HISTORY);
    assertFalse(hasFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS).matches(intent));
    assertFalse(
        hasFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
            .matches(intent));
  }

  @Test
  public void hasFlagsWithCustomFlagsDoesNotMatch() {
    Intent intent = new Intent();
    intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION | 8);
    assertFalse((hasFlags(16)).matches(intent));
    assertFalse((hasFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | 8)).matches(intent));
  }

  @Test
  public void filterEqualsMatches() {
    Intent intent =
        new Intent("foo.action", uri)
            .setType("text")
            .setClassName("com.foo.bar", "com.foo.bar.Baz")
            .addCategory("category");
    Intent intentWithFlagsAndExtras =
        new Intent(intent).addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION).putExtra("foo", "bar");
    assertTrue(filterEquals(intent).matches(intentWithFlagsAndExtras));
  }

  @Test
  public void filterEqualsDoesNotMatch() {
    Intent intent =
        new Intent("foo.action", uri)
            .setClassName("com.foo.bar", "com.foo.bar.Baz")
            .addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION)
            .setType("text");
    Intent intentWithDifferentAction = new Intent(intent).setAction("bar.action");
    assertFalse(filterEquals(intent).matches(intentWithDifferentAction));
    Intent intentWithDifferentData =
        new Intent(intent).setData(Uri.parse("https://www.google.com/search?q=NotMatcher"));
    assertFalse(filterEquals(intent).matches(intentWithDifferentData));
    Intent intentWithDifferentType = new Intent(intent).setType("img");
    assertFalse(filterEquals(intent).matches(intentWithDifferentType));
    Intent intentWithDifferentComponent =
        new Intent(intent).setClassName("com.foo.bar", "com.foo.bar.Other");
    assertFalse(filterEquals(intent).matches(intentWithDifferentComponent));
    Intent intentWithDifferentCategories = new Intent(intent).addCategory("category");
    assertFalse(filterEquals(intent).matches(intentWithDifferentCategories));
  }

  @Test
  public void isInternalTesting() {
    String targetPackage = getApplicationContext().getPackageName();
    ComponentName targetComponent = new ComponentName(targetPackage, targetPackage + ".SomeClass ");
    assertTrue(isInternal().matches(new Intent().setComponent(targetComponent)));
    assertFalse(not(isInternal()).matches(new Intent().setComponent(targetComponent)));
  }

  @Test
  public void isInternalDoesNotMatch() {
    assertFalse(isInternal().matches(new Intent())); // no target package
    ComponentName externalComponent =
        new ComponentName("com.google.android", "com.google.android.SomeClass");
    assertFalse(isInternal().matches(new Intent().setComponent(externalComponent)));
  }

  private class FakeResolvedIntent extends Intent implements ResolvedIntent {
    private final String pkg;

    FakeResolvedIntent(String pkg) {
      this.pkg = pkg;
    }

    @Override
    public boolean canBeHandledBy(String appPackage) {
      return appPackage.equals(pkg);
    }

    @Override
    public Intent getIntent() {
      return this;
    }
  }
  ;
}
