// Copyright (C) 2020 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.acceptance.server.mail;

import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;

import com.google.common.io.BaseEncoding;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.server.mail.EmailTokenVerifier;
import com.google.gerrit.server.mail.EmailTokenVerifier.InvalidTokenException;
import com.google.gerrit.server.mail.SignedToken;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.testing.ConfigSuite;
import java.nio.charset.StandardCharsets;
import org.eclipse.jgit.lib.Config;
import org.junit.Before;
import org.junit.Test;

public class SignedTokenEmailTokenVerifierIT extends AbstractDaemonTest {
  @ConfigSuite.Default
  public static Config defaultConfig() {
    Config cfg = new Config();
    cfg.setString("auth", null, "registerEmailPrivateKey", SignedToken.generateRandomKey());
    return cfg;
  }

  private SignedTokenEmailTokenVerifier signedTokenEmailTokenVerifier;

  @Before
  public void setUp() throws Exception {
    signedTokenEmailTokenVerifier =
        server
            .getTestInjector()
            .getBinding(SignedTokenEmailTokenVerifier.class)
            .getProvider()
            .get();
  }

  /** Test encode */
  @Test
  public void encode() throws Exception {
    String tokenString = signedTokenEmailTokenVerifier.encode(user.id(), user.email());
    int index = tokenString.indexOf("$");
    String text = tokenString.substring(index + 1);
    String textDecoded = new String(BaseEncoding.base64Url().decode(text), StandardCharsets.UTF_8);
    int pos = textDecoded.indexOf(":");
    assertThat(textDecoded.substring(0, pos)).isEqualTo(user.id().toString());
    assertThat(textDecoded.substring(pos + 1)).isEqualTo(user.email());
  }

  /** Test decode */
  @Test
  public void decode() throws Exception {
    String tokenString = signedTokenEmailTokenVerifier.encode(user.id(), user.email());
    String tokenKey = tokenString.substring(0, tokenString.indexOf("$"));
    String text = user.id() + ":" + user.email();
    String invalidTokenString =
        tokenKey + "$" + BaseEncoding.base64Url().encode(text.getBytes(StandardCharsets.UTF_8));
    EmailTokenVerifier.ParsedToken parsedToken =
        signedTokenEmailTokenVerifier.decode(invalidTokenString);
    assertThat(parsedToken.getAccountId()).isEqualTo(user.id());
    assertThat(parsedToken.getEmailAddress()).isEqualTo(user.email());
  }

  /** Test token format is wrong(without '$' to split key and text) */
  @Test
  public void invalidFormat() throws Exception {
    InvalidTokenException thrown =
        assertThrows(
            InvalidTokenException.class,
            () -> signedTokenEmailTokenVerifier.decode("Invalid token"));
    assertThat(thrown)
        .hasCauseThat()
        .hasMessageThat()
        .isEqualTo("Token does not contain character '$'");
  }

  /** Test input token string is empty or null */
  @Test
  public void emptyInput() throws Exception {
    InvalidTokenException thrownWithNull =
        assertThrows(InvalidTokenException.class, () -> signedTokenEmailTokenVerifier.decode(null));
    InvalidTokenException thrownWithEmpty =
        assertThrows(InvalidTokenException.class, () -> signedTokenEmailTokenVerifier.decode(""));
    assertThat(thrownWithNull).hasCauseThat().hasMessageThat().isEqualTo("Empty token");
    assertThat(thrownWithEmpty).hasCauseThat().hasMessageThat().isEqualTo("Empty token");
  }

  /** Test token format is right but key is an illegal BASE64 string */
  @Test
  public void illegalTokenKey() throws Exception {
    InvalidTokenException thrown =
        assertThrows(
            InvalidTokenException.class,
            () -> signedTokenEmailTokenVerifier.decode("Illegal token key$...."));
    assertThat(thrown).hasCauseThat().hasMessageThat().isEqualTo("Base64 decoding failed");
  }

  /** Test token text not match the required pattern */
  @Test
  public void tokenTextPatternMismatch() throws Exception {
    String tokenString = signedTokenEmailTokenVerifier.encode(user.id(), user.email());
    String tokenKey = tokenString.substring(0, tokenString.indexOf("$"));
    String pattern = user.id() + ":" + user.email();
    String invalidTokenTextPattern = tokenKey + "$" + pattern.replace(":", "");
    InvalidTokenException thrown =
        assertThrows(
            InvalidTokenException.class,
            () -> signedTokenEmailTokenVerifier.decode(invalidTokenTextPattern));
    assertThat(thrown).hasCauseThat().hasMessageThat().isEqualTo("Token text mismatch");
  }
}
