/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */


#include "pagespeed/kernel/base/source_map.h"

#include "pagespeed/kernel/base/gtest.h"

namespace net_instaweb {

namespace {

class SourceMapTest : public ::testing::Test {
 protected:
};

TEST_F(SourceMapTest, EncodeBase64) {
  EXPECT_EQ('A', source_map::EncodeBase64(0));
  EXPECT_EQ('B', source_map::EncodeBase64(1));
  EXPECT_EQ('Z', source_map::EncodeBase64(25));
  EXPECT_EQ('a', source_map::EncodeBase64(26));
  EXPECT_EQ('z', source_map::EncodeBase64(51));
  EXPECT_EQ('0', source_map::EncodeBase64(52));
  EXPECT_EQ('9', source_map::EncodeBase64(61));
  EXPECT_EQ('+', source_map::EncodeBase64(62));
  EXPECT_EQ('/', source_map::EncodeBase64(63));

  // Error cases
  EXPECT_DEBUG_DEATH(source_map::EncodeBase64(-1), "EncodeBase64");
  EXPECT_DEBUG_DEATH(source_map::EncodeBase64(64), "EncodeBase64");
  EXPECT_DEBUG_DEATH(source_map::EncodeBase64(100), "EncodeBase64");
  EXPECT_DEBUG_DEATH(source_map::EncodeBase64(-12345), "EncodeBase64");
  EXPECT_DEBUG_DEATH(source_map::EncodeBase64(54321), "EncodeBase64");
}

TEST_F(SourceMapTest, EncodeVlq) {
  EXPECT_EQ("A", source_map::EncodeVlq(0));   // 000000  ("A" in base64 binary)
  // Note: Nothing encodes to "B" (-0). AFAICT, it would be OK if 0 did, though.
  EXPECT_EQ("C", source_map::EncodeVlq(1));   // 000010
  EXPECT_EQ("D", source_map::EncodeVlq(-1));  // 000011
  EXPECT_EQ("E", source_map::EncodeVlq(2));   // 000100
  EXPECT_EQ("F", source_map::EncodeVlq(-2));  // 000101
  EXPECT_EQ("G", source_map::EncodeVlq(3));   // 000110
  EXPECT_EQ("H", source_map::EncodeVlq(-3));  // 000111

  EXPECT_EQ(  "R", source_map::EncodeVlq(-8));      // 010001
  EXPECT_EQ(  "a", source_map::EncodeVlq(13));      // 011010
  EXPECT_EQ( "0C", source_map::EncodeVlq(42));      // 110100 000010
  EXPECT_EQ( "0I", source_map::EncodeVlq(138));     // 110100 001000
  EXPECT_EQ( "9d", source_map::EncodeVlq(-478));    // 111101 011101
  EXPECT_EQ("7yB", source_map::EncodeVlq(-813));    // 111011 110010 000001
  EXPECT_EQ("+/X", source_map::EncodeVlq(12287));   // 111110 111111 010111
  EXPECT_EQ("jib", source_map::EncodeVlq(-13857));  // 100011 100010 011011
  // 100000 101000 101101 011110
  EXPECT_EQ("gote", source_map::EncodeVlq(498304));
}

TEST_F(SourceMapTest, VlqExtremeVals) {
  // Test extreme values.
  // From https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit:
  // Note: The values that can be represent by the VLQ Base64 encoded are
  // limited to 32 bit quantities until some use case for larger values is
  // presented.

  // 111110 111111 111111 111111 111111 111111 000011
  EXPECT_EQ("+/////D", source_map::EncodeVlq(kint32max));
  // 100001 100000 100000 100000 100000 100000 000100
  EXPECT_EQ("hgggggE", source_map::EncodeVlq(kint32min));
}

TEST_F(SourceMapTest, EncodeMappings) {
  source_map::MappingVector mappings;
  mappings.push_back(source_map::Mapping(1,   1,  0,  10,   0));
  mappings.push_back(source_map::Mapping(1,  21,  1,  11,   0));
  mappings.push_back(source_map::Mapping(1,  25,  1,  11,  81));
  mappings.push_back(source_map::Mapping(2,  13,  2,  11, 105));
  mappings.push_back(source_map::Mapping(5,   8, 13, 132,   7));
  mappings.push_back(source_map::Mapping(5, 472,  0, 436,  13));

  GoogleString result;
  EXPECT_TRUE(source_map::EncodeMappings(mappings, &result));
  EXPECT_EQ(";CAUA,oBCCA,IAAiF;aCAwB;;;QWyHlG,gdbgTM", result);
}

// Trivial example, to make sure code works at all.
TEST_F(SourceMapTest, Encode_Simple) {
  source_map::MappingVector mappings;
  GoogleString result;
  EXPECT_TRUE(source_map::Encode("http://example.com/generated.js",
                                 "http://example.com/original.js",
                                 mappings,
                                 &result));
  // Note: Exact order and amount of whitespace is not important and this may
  // need to be re-golded if the Json::FastWriter changes.
  EXPECT_EQ(")]}'\n"
            "{\"file\":\"http://example.com/generated.js\",\"mappings\":\"\","
            "\"names\":[],\"sources\":[\"http://example.com/original.js\"],"
            "\"version\":3}\n", result);
}

TEST_F(SourceMapTest, Encode) {
  source_map::MappingVector mappings;
  mappings.push_back(source_map::Mapping(0,  0, 0, 4,  0));
  mappings.push_back(source_map::Mapping(0, 21, 0, 4, 22));
  mappings.push_back(source_map::Mapping(0, 22, 0, 5,  2));
  mappings.push_back(source_map::Mapping(0, 44, 0, 6,  0));

  GoogleString result;
  EXPECT_TRUE(source_map::Encode("http://example.com/generated.js",
                                 "http://example.com/original.js",
                                 mappings,
                                 &result));
  // Note: Exact order and amount of whitespace is not important and this may
  // need to be re-golded if the Json::FastWriter changes.
  EXPECT_EQ(")]}'\n"
            "{\"file\":\"http://example.com/generated.js\","
            "\"mappings\":\"AAIA,qBAAsB,CACpB,sBACF\","
            "\"names\":[],\"sources\":[\"http://example.com/original.js\"],"
            "\"version\":3}\n", result);
}

// Make sure chars are escaped correctly in JSON string.
TEST_F(SourceMapTest, Encode_JsonEscaping) {
  source_map::MappingVector mappings;
  GoogleString result;
  EXPECT_TRUE(source_map::Encode("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?",
                                 "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"
                                 "\x0c\x0d\x10\x13\x1f\x20\x7e\x7f",
                                 mappings,
                                 &result));
  // Note: Exact order and amount of whitespace is not important and this may
  // need to be re-golded if the Json::FastWriter changes.
  EXPECT_EQ(")]}'\n"
            // " and \ are backslash escaped (' isn't).
            // < and > are URL %XX escaped to avoid XSS shenanigans.
            "{\"file\":\"`~!@#$%^&*()-_=+[{]}\\\\|;:'\\\",%3C.%3E/?\","
            "\"mappings\":\"\",\"names\":[],\"sources\":[\""
            // Control chars U+00 to U+1F must be escaped as \uXXXX
            // Except for a few special cases: \b \f \n \r \t
            // U+7F is not considered a control char, not escaped.
            "\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n"
            "\\u000B\\f\\r\\u0010\\u0013\\u001F ~\x7F\"],"
            "\"version\":3}\n", result);
}

TEST_F(SourceMapTest, Encode_Fail) {
  source_map::MappingVector mappings;
  mappings.push_back(source_map::Mapping(1, 0, 0, 0, 0));
  // Invalid: mappings must be sorted.
  mappings.push_back(source_map::Mapping(0, 0, 0, 0, 0));

  GoogleString result;
  EXPECT_DEBUG_DEATH({
      EXPECT_FALSE(source_map::Encode("http://example.com/generated.js",
                                      "http://example.com/original.js",
                                      mappings,
                                      &result));
    }, "Mappings are not sorted");
}

}  // namespace

}  // namespace net_instaweb
