Repository: envoyproxy/envoy
File: test/extensions/filters/http/ext_proc/mutation_utils_test.cc (C++)

Code before:
      {"h4", "v4"},
  };
  headers.setMaxHeadersCount(5);
  // We're now at 4 headers. One more mutation will put us at the limit,
  // and a second will put us over.

  envoy::service::ext_proc::v3::HeaderMutation mutation;
  auto* s = mutation.add_set_headers();
  s->mutable_header()->set_key("h5");
  s->mutable_header()->set_raw_value("v5");

  Checker checker(HeaderMutationRules::default_instance(), regex_engine_);
  Envoy::Stats::MockCounter rejections;

  auto status = MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections);
  EXPECT_TRUE(status.ok());

  s = mutation.add_set_headers();
  s->mutable_header()->set_key("h6");
  s->mutable_header()->set_raw_value("v6");
  EXPECT_CALL(rejections, inc());
  EXPECT_THAT(MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections),
              HasStatus(absl::StatusCode::kInvalidArgument, "header_mutation_count_exceeded"));
  // Surprise: While we return an error, the headers actually DO get mutated.
  EXPECT_EQ(headers.size(), 6);
}

TEST_F(MutationUtilsTest, TestHeaderMutationRemoveOperationExceedsMaxCount) {
  TestHeaderMapImplWithOverrides headers{
      {"h1", "v1"},
      {"h2", "v2"},
  };
  headers.setMaxHeadersCount(1);

  envoy::service::ext_proc::v3::HeaderMutation mutation;
  mutation.add_remove_headers("h1");
  mutation.add_remove_headers("h2");

  Checker checker(HeaderMutationRules::default_instance(), regex_engine_);
  Envoy::Stats::MockCounter rejections;
  EXPECT_CALL(rejections, inc());
  EXPECT_THAT(
      MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections),
      HasStatus(absl::StatusCode::kInvalidArgument, "header_mutation_remove_count_exceeded"));
  EXPECT_EQ(headers.size(), 2);
}

TEST_F(MutationUtilsTest, TestHeaderMutationExceedsMaxKb) {
  TestHeaderMapImplWithOverrides headers;
  headers.setMaxHeadersKb(1);
  // Fill up the headers part of the way to the 1kb limit

Review comment (bug):
indeed it will be a surprise when the partial mutations got applied and sent to upstream/downstream as usual, when fail-open is configured AND the error is a FailedPrecondition error. 

If the error code is like InvalidArgument, it ends up with a crispy termination of the http request back to downstream/upstream, the partial mutation is dropped on the ground. 

Code after:
      {"h3", "v3"},
      {"h4", "v4"},
  };
  headers.setMaxHeadersCount(5);
  // We're now at 4 headers. One more mutation will put us at the limit,
  // and a second will put us over.

  envoy::service::ext_proc::v3::HeaderMutation mutation;
  auto* s = mutation.add_set_headers();
  s->mutable_header()->set_key("h5");
  s->mutable_header()->set_raw_value("v5");

  Checker checker(HeaderMutationRules::default_instance(), regex_engine_);
  Envoy::Stats::MockCounter rejections;

  auto status = MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections);
  EXPECT_TRUE(status.ok());

  s = mutation.add_set_headers();
  s->mutable_header()->set_key("h6");
  s->mutable_header()->set_raw_value("v6");
  EXPECT_CALL(rejections, inc());
  EXPECT_THAT(
      MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections),
      HasStatus(absl::StatusCode::kInvalidArgument, "header_mutation_result_exceeds_limit"));
  // Surprise: While we return an error, the headers actually DO get mutated.
  // (Filter must detect the error status and discard the mutation.)
  EXPECT_EQ(headers.size(), 6);
}

TEST_F(MutationUtilsTest, TestHeaderMutationRemoveOperationExceedsMaxCount) {
  TestHeaderMapImplWithOverrides headers{
      {"h1", "v1"},
      {"h2", "v2"},
  };
  headers.setMaxHeadersCount(1);

  envoy::service::ext_proc::v3::HeaderMutation mutation;
  mutation.add_remove_headers("h1");
  mutation.add_remove_headers("h2");

  Checker checker(HeaderMutationRules::default_instance(), regex_engine_);
  Envoy::Stats::MockCounter rejections;
  EXPECT_CALL(rejections, inc());
  EXPECT_THAT(MutationUtils::applyHeaderMutations(mutation, headers, false, checker, rejections),
              HasStatus(absl::StatusCode::kInvalidArgument,
                        "header_mutation_operation_count_exceeds_limit"));
  EXPECT_EQ(headers.size(), 2);
}

TEST_F(MutationUtilsTest, TestHeaderMutationExceedsMaxKb) {