126 lines
5.0 KiB
Python
126 lines
5.0 KiB
Python
#
|
|
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
import json
|
|
import os
|
|
from dataclasses import dataclass
|
|
from logging import getLogger
|
|
from typing import List
|
|
from unittest import mock
|
|
|
|
import requests
|
|
from airbyte_cdk.sources.utils.sentry import AirbyteSentry
|
|
from sentry_sdk.transport import Transport
|
|
|
|
|
|
@mock.patch("airbyte_cdk.sources.utils.sentry.sentry_sdk")
|
|
def test_sentry_init_no_env(sentry_mock):
|
|
assert AirbyteSentry.DSN_ENV_NAME not in os.environ
|
|
AirbyteSentry.init("test_source")
|
|
assert not sentry_mock.init.called
|
|
assert not AirbyteSentry.sentry_enabled
|
|
AirbyteSentry.set_tag("tagname", "value")
|
|
assert not sentry_mock.set_tag.called
|
|
AirbyteSentry.add_breadcrumb("msg", data={})
|
|
assert not sentry_mock.add_breadcrumb.called
|
|
|
|
with AirbyteSentry.start_transaction("name", "op"):
|
|
assert not sentry_mock.start_transaction.called
|
|
|
|
with AirbyteSentry.start_transaction_span("name", "op"):
|
|
assert not sentry_mock.start_span.called
|
|
|
|
|
|
@mock.patch.dict(os.environ, {AirbyteSentry.DSN_ENV_NAME: "dsn"})
|
|
@mock.patch("airbyte_cdk.sources.utils.sentry.sentry_sdk")
|
|
def test_sentry_init(sentry_mock):
|
|
AirbyteSentry.init("test_source")
|
|
assert sentry_mock.init.called
|
|
sentry_mock.set_tag.assert_any_call("source", "test_source")
|
|
sentry_mock.set_tag.assert_any_call("run_id", mock.ANY)
|
|
assert AirbyteSentry.sentry_enabled
|
|
AirbyteSentry.set_tag("tagname", "value")
|
|
assert sentry_mock.set_tag.called
|
|
AirbyteSentry.add_breadcrumb("msg", data={})
|
|
assert sentry_mock.add_breadcrumb.called
|
|
with AirbyteSentry.start_transaction("name", "op"):
|
|
assert sentry_mock.start_transaction.called
|
|
|
|
with AirbyteSentry.start_transaction_span("name", "op"):
|
|
assert sentry_mock.start_span.called
|
|
|
|
|
|
@dataclass
|
|
class TestTransport(Transport):
|
|
secrets: List[str]
|
|
# Sentry sdk wraps sending event with try except that would intercept
|
|
# AssertionError exception resulting it would ignore assert directive.
|
|
# Use this variable to check if test failed after sentry code executed.
|
|
failed = None
|
|
|
|
def capture_envelope(self, envelop):
|
|
for s in self.secrets:
|
|
for i in envelop.items:
|
|
payload = json.dumps(i.payload.json)
|
|
assert s not in payload
|
|
|
|
def capture_event(self, event):
|
|
if self.failed:
|
|
return
|
|
event = json.dumps(event)
|
|
for s in self.secrets:
|
|
if s in event:
|
|
self.failed = f"{s} should not be in {event}"
|
|
return
|
|
|
|
|
|
@mock.patch.dict(os.environ, {AirbyteSentry.DSN_ENV_NAME: "https://22222@222.ingest.sentry.io/111"})
|
|
def test_sentry_sensitive_info(httpserver):
|
|
SECRET = "SOME_secret"
|
|
UNEXPECTED_SECRET = "UnexEpectedSecret"
|
|
SECRETS = [SECRET]
|
|
transport = TestTransport(secrets=[*SECRETS, UNEXPECTED_SECRET])
|
|
|
|
AirbyteSentry.init("test_source", transport=transport, secret_values=SECRETS)
|
|
|
|
AirbyteSentry.add_breadcrumb("msg", {"crumb": SECRET})
|
|
AirbyteSentry.set_context("my secret", {"api_key": SECRET})
|
|
AirbyteSentry.capture_message(f"this is {SECRET}")
|
|
AirbyteSentry.capture_message(f"Issue url http://localhost:{httpserver.port}/test?api_key={UNEXPECTED_SECRET}")
|
|
AirbyteSentry.capture_message(f"Issue url http://localhost:{httpserver.port}/test?access_token={UNEXPECTED_SECRET}")
|
|
AirbyteSentry.capture_message(f"Issue url http://localhost:{httpserver.port}/test?refresh_token={UNEXPECTED_SECRET}")
|
|
AirbyteSentry.set_context("headers", {"Authorization": f"Bearer {UNEXPECTED_SECRET}"})
|
|
getLogger("airbyte").info(f"this is {SECRET}")
|
|
requests.get(
|
|
f"http://localhost:{httpserver.port}/test?api_key={SECRET}",
|
|
headers={"Authorization": f"Bearer {SECRET}"},
|
|
).text
|
|
requests.get(
|
|
f"http://localhost:{httpserver.port}/test?api_key={UNEXPECTED_SECRET}",
|
|
headers={"Authorization": f"Bearer {UNEXPECTED_SECRET}"},
|
|
).text
|
|
AirbyteSentry.capture_exception(Exception(f"Secret info: {SECRET}"))
|
|
assert not transport.failed
|
|
|
|
|
|
@mock.patch.dict(os.environ, {AirbyteSentry.DSN_ENV_NAME: "https://22222@222.ingest.sentry.io/111"})
|
|
def test_sentry_sensitive_info_transactions(httpserver):
|
|
SECRET = "SOME_secret"
|
|
SECRETS = [SECRET]
|
|
UNEXPECTED_SECRET = "UnexEpectedSecret"
|
|
transport = TestTransport(secrets=[*SECRETS, UNEXPECTED_SECRET])
|
|
AirbyteSentry.init("test_source", transport=transport, secret_values=SECRETS)
|
|
|
|
AirbyteSentry.set_context("my secret", {"api_key": SECRET})
|
|
AirbyteSentry.set_context("headers", {"Authorization": f"Bearer {UNEXPECTED_SECRET}"})
|
|
with AirbyteSentry.start_transaction("name", "op"):
|
|
with AirbyteSentry.start_transaction_span(
|
|
"name", description=f"http://localhost:{httpserver.port}/test?api_key={UNEXPECTED_SECRET}"
|
|
):
|
|
requests.get(
|
|
f"http://localhost:{httpserver.port}/test?api_key={SECRET}",
|
|
headers={"Authorization": f"Bearer {SECRET}"},
|
|
).text
|
|
assert not transport.failed
|