Skip to content

Commit

Permalink
🎉 Source e2e test: support custom catalog (airbytehq#9720)
Browse files Browse the repository at this point in the history
* Add continuous feed mode to source e2e-test

* Update connector catalog doc

* Fix sonar qube issues

* Add cloud variant

* Format code

* Add testing source connector to seed
  • Loading branch information
tuliren authored Jan 24, 2022
1 parent 05c84f5 commit b269b9f
Show file tree
Hide file tree
Showing 35 changed files with 1,841 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@
documentationUrl: https://docs.airbyte.io/integrations/sources/drift
icon: drift.svg
sourceType: api
- name: E2E Testing
sourceDefinitionId: d53f9084-fa6b-4a5a-976c-5b8392f4ad8a
dockerRepository: airbyte/source-e2e-test
dockerImageTag: 1.0.0
documentationUrl: https://docs.airbyte.io/integrations/sources/e2e-test
icon: airbyte.svg
sourceType: api
- name: Exchange Rates Api
sourceDefinitionId: e2b40e36-aa0e-4bed-b41b-bcea6fa348b1
dockerRepository: airbyte/source-exchange-rates
Expand Down
148 changes: 148 additions & 0 deletions airbyte-config/init/src/main/resources/seed/source_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,154 @@
oauthFlowOutputParameters:
- - "access_token"
- - "refresh_token"
- dockerImage: "airbyte/source-e2e-test:1.0.0"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/e2e-test"
connectionSpecification:
$schema: "http://json-schema.org/draft-07/schema#"
title: "E2E Test Source Spec"
type: "object"
required:
- "type"
oneOf:
- title: "Continuous Feed"
required:
- "type"
- "max_messages"
- "mock_catalog"
description: "A mock mode that will emit random messages based on the input\
\ schema."
additionalProperties: false
properties:
type:
type: "string"
const: "CONTINUOUS_FEED"
default: "CONTINUOUS_FEED"
order: 10
max_messages:
title: "Max Records"
description: "Number of records to emit per stream. Min 1. Max 100 billion."
type: "integer"
default: 100
min: 1
max: 100000000000
order: 20
seed:
title: "Random Seed"
description: "When the seed is unspecified, the current time millis will\
\ be used as the seed. Range: [0, 1000000]."
type: "integer"
default: 0
examples:
- 42
min: 0
max: 1000000
order: 30
message_interval_ms:
title: "Message Interval (ms)"
description: "Interval between messages in ms. Min 0 ms. Max 60000 ms\
\ (1 minute)."
type: "integer"
min: 0
max: 60000
default: 0
order: 40
mock_catalog:
title: "Mock Catalog"
type: "object"
order: 50
oneOf:
- title: "Single Stream"
description: "A catalog with one stream."
required:
- "type"
- "stream_name"
- "stream_schema"
properties:
type:
type: "string"
const: "SINGLE_STREAM"
default: "SINGLE_STREAM"
stream_name:
title: "Stream Name"
description: "Name of the data stream."
type: "string"
default: "data_stream"
stream_schema:
title: "Stream Schema"
description: "A Json schema for the stream. The schema should be\
\ compatible with <a href=\"https://json-schema.org/draft-07/json-schema-release-notes.html\"\
>draft-07</a>. See <a href=\"https://cswr.github.io/JsonSchema/spec/introduction/\"\
>this doc</a> for examples."
type: "string"
default: "{ \"type\": \"object\", \"properties\": { \"column1\"\
: { \"type\": \"string\" } } }"
- title: "Multi-Stream"
description: "A catalog with multiple data streams."
required:
- "type"
- "stream_schemas"
properties:
type:
type: "string"
const: "MULTI_STREAM"
default: "MULTI_STREAM"
stream_schemas:
title: "Streams and Schemas"
description: "A Json object specifying multiple data streams and\
\ their schemas. Each key in this object is one stream name. Each\
\ value is the schema for that stream. The schema should be compatible\
\ with <a href=\"https://json-schema.org/draft-07/json-schema-release-notes.html\"\
>draft-07</a>. See <a href=\"https://cswr.github.io/JsonSchema/spec/introduction/\"\
>this doc</a> for examples."
type: "string"
default: "{ \"stream1\": { \"type\": \"object\", \"properties\"\
: { \"field1\": { \"type\": \"string\" } } }, \"stream2\": { \"\
type\": \"object\", \"properties\": { \"field1\": { \"type\":\
\ \"boolean\" } } } }"
- title: "Legacy Exception After N"
required:
- "type"
- "throw_after_n_records"
description: "A legacy mode from v0.1.1 mainly for unit tests. The catalog\
\ has one \"data\" stream, which has one string field \"column1\". This\
\ mode will throw an exception after N messages."
additionalProperties: false
properties:
type:
type: "string"
const: "EXCEPTION_AFTER_N"
default: "EXCEPTION_AFTER_N"
throw_after_n_records:
title: "Throw After N Records"
description: "Number of records to emit before throwing an exception.\
\ Min 1."
type: "integer"
min: 1
- title: "Legacy Infinite Feed"
required:
- "type"
- "max_records"
description: "A legacy mode from v0.1.1 mainly for unit tests. The catalog\
\ has one \"data\" stream, which has one string field \"column1\". This\
\ mode will emit messages infinitely."
additionalProperties: true
properties:
type:
type: "string"
const: "INFINITE_FEED"
default: "INFINITE_FEED"
max_records:
title: "Max Records"
description: "Number of records to emit. If not set, defaults to infinity."
type: "integer"
message_interval:
title: "Message Interval"
description: "Interval between messages in ms."
type: "integer"
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
- dockerImage: "airbyte/source-exchange-rates:0.2.5"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/sources/exchangeratesapi"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,12 @@ public void testDiscover() throws Exception {
public void testFullRefreshRead() throws Exception {
final ConfiguredAirbyteCatalog catalog = withFullRefreshSyncModes(getConfiguredCatalog());
final List<AirbyteMessage> allMessages = runRead(catalog);
final List<AirbyteMessage> recordMessages = allMessages.stream().filter(m -> m.getType() == Type.RECORD).collect(Collectors.toList());
final List<AirbyteRecordMessage> recordMessages = filterRecords(allMessages);
// the worker validates the message formats, so we just validate the message content
// We don't need to validate message format as long as we use the worker, which we will not want to
// do long term.
assertFalse(recordMessages.isEmpty(), "Expected a full refresh sync to produce records");
assertRecordMessages(recordMessages);

final List<String> regexTests = getRegexTests();
final List<String> stringMessages = allMessages.stream().map(Jsons::serialize).collect(Collectors.toList());
Expand All @@ -175,6 +176,10 @@ public void testFullRefreshRead() throws Exception {
});
}

protected void assertRecordMessages(final List<AirbyteRecordMessage> recordMessages) {
// do nothing by default
}

/**
* Configuring all streams in the input catalog to full refresh mode, performs two read operations
* on all streams which support full refresh syncs. It then verifies that the RECORD messages output
Expand Down
1 change: 1 addition & 0 deletions airbyte-integrations/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
| Close.com | [![source-close-com](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-close-com%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-close-com/) |
| Dixa | [![source-dixa](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-dixa%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-dixa) |
| Drift | [![source-drift](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-drift%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-drift) |
| End-to-End Testing | [![source-e2e-test](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-e2e-test%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-e2e-test) |
| Exchange Rates API | [![source-exchange-rates](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-exchange-rates%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-exchange-rates) |
| Facebook Marketing | [![source-facebook-marketing](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-facebook-marketing%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-facebook-marketing) |
| Files | [![source-file](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-file%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-file) |
Expand Down
11 changes: 4 additions & 7 deletions airbyte-integrations/connectors/destination-e2e-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ This is the repository for the Null destination connector in Java. For informati
#### Building via Gradle
From the Airbyte repository root, run:
```
./gradlew :airbyte-integrations:connectors:destination-:build
./gradlew :airbyte-integrations:connectors:destination-e2e-test:build
```

#### Create credentials
**If you are a community contributor**, generate the necessary credentials and place them in `secrets/config.json` conforming to the spec file in `src/main/resources/spec.json`.
Note that the `secrets` directory is git-ignored by default, so there is no danger of accidentally checking in sensitive information.

**If you are an Airbyte core member**, follow the [instructions](https://docs.airbyte.io/connector-development#using-credentials-in-ci) to set up the credentials.
No credential is needed for this connector.

### Locally running the connector docker image

Expand All @@ -35,8 +32,8 @@ docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-e2e-test:dev disc
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/destination-e2e-test:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
```

#### Dev Null Destination
The Dev Null Destination depends on this connector. It only allows the "silent" mode. When this mode is changed, please make sure that the Dev Null Destination is updated and published accordingly as well.
#### Cloud variant
The cloud variant of this connector is Dev Null Destination. It only allows the "silent" mode. When this mode is changed, please make sure that the Dev Null Destination is updated and published accordingly as well.

## Testing
We use `JUnit` for Java tests.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!Dockerfile
!build
20 changes: 20 additions & 0 deletions airbyte-integrations/connectors/source-e2e-test-cloud/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM airbyte/integration-base-java:dev AS build

WORKDIR /airbyte

ENV APPLICATION source-e2e-test-cloud

COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar

RUN tar xf ${APPLICATION}.tar --strip-components=1 && rm -rf ${APPLICATION}.tar

FROM airbyte/integration-base-java:dev

WORKDIR /airbyte

ENV APPLICATION source-e2e-test-cloud

COPY --from=build /airbyte /airbyte

LABEL io.airbyte.version=1.0.0
LABEL io.airbyte.name=airbyte/source-e2e-test-cloud
67 changes: 67 additions & 0 deletions airbyte-integrations/connectors/source-e2e-test-cloud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# End-to-End Testing Source Cloud Variant

This is the Cloud variant of the [E2E Test Source](https://docs.airbyte.io/integrations/sources/e2e-test). It only allows the "continuous feed" mode with finite number of record messages.

## Local development

#### Building via Gradle
From the Airbyte repository root, run:
```
./gradlew :airbyte-integrations:connectors:source-e2e-test-cloud:build
```

#### Create credentials
No credential is needed for this connector.

### Locally running the connector docker image

#### Build
Build the connector image via Gradle:
```
./gradlew :airbyte-integrations:connectors:source-e2e-test-cloud:airbyteDocker
```
When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in
the Dockerfile.

#### Run
Then run any of the connector commands as follows:
```
docker run --rm airbyte/source-e2e-test-cloud:dev spec
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-e2e-test-cloud:dev check --config /secrets/config.json
docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-e2e-test-cloud:dev discover --config /secrets/config.json
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-e2e-test-cloud:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
```

#### Cloud variant
The cloud version of this connector only allows the `CONTINUOUS FEED` mode. When this mode is changed, please make sure that the cloud variant is updated and published accordingly as well.

## Testing
We use `JUnit` for Java tests.

### Unit and Integration Tests
Place unit tests under `src/test/io/airbyte/integrations/sources/e2e-test`.

#### Acceptance Tests
Airbyte has a standard test suite that all destination connectors must pass. See example(s) in
`src/test-integration/java/io/airbyte/integrations/sources/e2e-test/`.

### Using gradle to run tests
All commands should be run from airbyte project root.
To run unit tests:
```
./gradlew :airbyte-integrations:connectors:sources-e2e-test:unitTest
```
To run acceptance and custom integration tests:
```
./gradlew :airbyte-integrations:connectors:sources-e2e-test:integrationTest
```

## Dependency Management

### Publishing a new version of the connector
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
1. Make sure your changes are passing unit and integration tests.
2. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)).
3. Create a Pull Request.
4. Pat yourself on the back for being an awesome contributor.
5. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
28 changes: 28 additions & 0 deletions airbyte-integrations/connectors/source-e2e-test-cloud/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id 'application'
id 'airbyte-docker'
id 'airbyte-integration-test-java'
}

application {
mainClass = 'io.airbyte.integrations.source.e2e_test.CloudTestingSources'
}

dependencies {
implementation project(':airbyte-config:models')
implementation project(':airbyte-protocol:models')
implementation project(':airbyte-integrations:bases:base-java')
implementation project(':airbyte-integrations:connectors:source-e2e-test')
implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs)

testImplementation project(":airbyte-json-validation")

integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-source-test')
integrationTestJavaImplementation project(':airbyte-integrations:connectors:source-e2e-test-cloud')
}

allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
Loading

0 comments on commit b269b9f

Please sign in to comment.