diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index 2750cd17b1..2ba3593470 100755 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -1,53 +1,53 @@ -name: Bug report -description: Create a bug report to help us improve Tailcall -title: "[bug]: " -labels: [bug] -body: -- type: markdown - attributes: - value: | - Thank you for taking the time to fill out this bug report. -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the bug you encountered - options: - - label: I have searched the existing issues - required: true -- type: textarea - attributes: - label: Current behavior - description: A concise description of what you're experiencing and what you expect - placeholder: | - When I do , happens and I see the error message attached below: - ```...``` - What I expect is - validations: - required: true -- type: textarea - attributes: - label: Steps to reproduce - description: Add steps to reproduce this behaviour, include console or network logs and screenshots - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - -- type: dropdown - id: repository - attributes: - label: Repository - options: - - tailcall - - tailcallhq.github.io - - homebrew-tailcall - - tailcall-on-aws - validations: - required: true -- type: markdown - attributes: - value: | - Please add the respective Label to the Issue \ No newline at end of file +name: Bug report +description: Create a bug report to help us improve Tailcall +title: "[bug]: " +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to fill out this bug report. + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered + options: + - label: I have searched the existing issues + required: true + - type: textarea + attributes: + label: Current behavior + description: A concise description of what you're experiencing and what you expect + placeholder: | + When I do , happens and I see the error message attached below: + ```...``` + What I expect is + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce + description: Add steps to reproduce this behaviour, include console or network logs and screenshots + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: dropdown + id: repository + attributes: + label: Repository + options: + - tailcall + - tailcallhq.github.io + - homebrew-tailcall + - tailcall-on-aws + validations: + required: true + - type: markdown + attributes: + value: | + Please add the respective Label to the Issue diff --git a/.github/ISSUE_TEMPLATE/documentation-update.yaml b/.github/ISSUE_TEMPLATE/documentation-update.yaml index b3c19efaa9..0ef9e5442e 100755 --- a/.github/ISSUE_TEMPLATE/documentation-update.yaml +++ b/.github/ISSUE_TEMPLATE/documentation-update.yaml @@ -29,4 +29,4 @@ body: - type: markdown attributes: value: | - Please add the respective Label to the Issue \ No newline at end of file + Please add the respective Label to the Issue diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml index 8fd104e227..30fb459e0b 100755 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -1,43 +1,43 @@ -name: Feature request -description: Suggest a feature to improve Tailcall -title: "[feature]: " -labels: [Enhancement] -body: -- type: markdown - attributes: - value: | - Thank you for taking the time to request a feature for Tailcall -- type: checkboxes - attributes: - label: Is there an existing feature request for this? - description: Please search to see if an issue related to this feature request/feature request already exists - options: - - label: I have searched the existing issues - required: true -- type: textarea - attributes: - label: Summary - description: One paragraph description of the feature - validations: - required: true -- type: textarea - attributes: - label: Why should this be worked on? - description: A concise description of the problems or use cases for this feature request - validations: - required: true -- type: dropdown - id: repository - attributes: - label: Repository - options: - - tailcall - - tailcallhq.github.io - - homebrew-tailcall - - tailcall-on-aws - validations: - required: true -- type: markdown - attributes: - value: | - Please add the respective Label to the Issue +name: Feature request +description: Suggest a feature to improve Tailcall +title: "[feature]: " +labels: [Enhancement] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to request a feature for Tailcall + - type: checkboxes + attributes: + label: Is there an existing feature request for this? + description: Please search to see if an issue related to this feature request/feature request already exists + options: + - label: I have searched the existing issues + required: true + - type: textarea + attributes: + label: Summary + description: One paragraph description of the feature + validations: + required: true + - type: textarea + attributes: + label: Why should this be worked on? + description: A concise description of the problems or use cases for this feature request + validations: + required: true + - type: dropdown + id: repository + attributes: + label: Repository + options: + - tailcall + - tailcallhq.github.io + - homebrew-tailcall + - tailcall-on-aws + validations: + required: true + - type: markdown + attributes: + value: | + Please add the respective Label to the Issue diff --git a/.github/codespell-ignore b/.github/codespell-ignore new file mode 100644 index 0000000000..d106fccd0f --- /dev/null +++ b/.github/codespell-ignore @@ -0,0 +1,2 @@ +crate +unit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 091d4da9f3..6f6cdf1695 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,8 @@ jobs: - name: Check spelling uses: codespell-project/actions-codespell@v2 with: + ignore_words_file: .github/codespell-ignore skip: .git,**/*.png,**/*.gif,**/*.jpg,**/*.svg,**/*.ico,**/*.json,package.json,package-lock.json,*.lock - ignore_words_list: unit - name: Install and Build 🔧 run: | diff --git a/docs/contributors/_category_.json b/docs/contributors/_category_.json new file mode 100644 index 0000000000..9f4171e1dd --- /dev/null +++ b/docs/contributors/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Contributors", + "position": 6 +} diff --git a/docs/contributors/bounty.md b/docs/contributors/bounty.md new file mode 100644 index 0000000000..268f7d4db4 --- /dev/null +++ b/docs/contributors/bounty.md @@ -0,0 +1,39 @@ +--- +title: "Bounty" +description: "This guide outlines the Tailcall Bounty Program, emphasizing quick, high-quality contributions and collaborative engagement through challenges and rewards. It provides essential participation rules and encourages active community involvement for innovation and excellence." +sidebar_position: 2 +--- + +Hey there! We're thrilled that you're considering joining our Bounty Program. This is your chance to showcase your skills and get rewarded for them. Here's everything you need to know to dive in and make a splash. + +## Our Philosophy + +We’re all about meritocracy here. That means the best ideas and implementations win! We love seeing your quality work and fast moves. And yes, we recognize and appreciate your efforts when you exceed expectations. + +## Quick & Quality + +- **Speedy Gonzalez:** We like fast results! Quick feedback, quick updates. The faster, the better! +- **A+ Quality:** But hey, don’t rush it if it means cutting corners. We want your best – make it shine! + +## Teamwork Makes the Dream Work + +- **Join Us on Discord:** Our [Discord](https://discord.gg/kRZBPpkgwq) server is THE place to collaborate, get tips, and find your next bestie. Let’s make magic together. +- **Share the Love:** Inspired by someone’s PR? Working together? Feel free to [/split](https://docs.algora.io/commands) that bounty – sharing is caring! + +## How to Dive In + +1. **Pick a Challenge:** Use `/attempt` in the comments to call dibs on an issue. It’s like saying “I got this!” +2. **Show Your Work:** Got an issue? Great! Now, whip up a draft PR within 24 hours to show you’re on it. +3. **Go for Gold:** Once you’re ready, switch that draft to Ready for Review. Make sure it’s polished and gleaming! +4. **Extra Mile Alert:** We’ve got bonuses for those who add that special touch. Clean up, optimize, or fix something extra? We’re here for it! + +## The Rules of the Game + +- **Be Quick or Be… Late:** No PR within 24 hours? Then it’s open season for that issue again. +- **There Can Be One Winner:** Multiple folks can try, but it's the top PR that emerges victorious. If there's a tie, the first submission takes the prize. +- **No Copycats:** Be original; be yourself. Avoid merely copying someone else's hard work. +- **Consider Before You Contribute:** If there’s already a PR in progress, perhaps take a moment to review it before submitting your own. + +## Wrapping Up + +We’re stoked to have you! This program is your chance to shine and get rewarded while at it. Stick to these friendly guidelines, and let’s make something awesome together. We look forward to seeing what you bring to the table. diff --git a/docs/contributors/guidelines.md b/docs/contributors/guidelines.md new file mode 100644 index 0000000000..f6811306bb --- /dev/null +++ b/docs/contributors/guidelines.md @@ -0,0 +1,152 @@ +--- +title: "Guidelines" +description: "This guide outlines the key steps and best practices for contributing to the Tailcall project, covering setup, coding, testing, and documentation to ensure quality and consistency in contributions." +sidebar_position: 1 +--- + +# Contribution Guidelines + +Thank you for considering contributing to **Tailcall**! This document outlines the steps and guidelines to follow when contributing to this project. + +## Getting Started + +1. **Fork the Repository:** Start by forking the repository to your personal account on GitHub. +2. **Clone the Forked Repository:** Once you have forked the repository, clone it to your local machine. + ```bash + git clone https://github.com/tailcallhq/tailcall.git + ``` + +## Setting Up the Development Environment + +1. **Install Rust:** If you haven't already, install Rust using [rustup](https://rustup.rs/). Install the `nightly` toolchain as well, as it's used for linting. +2. **Install Prettier:** Install [Prettier](https://prettier.io/) too as this is also used for linting. +3. **Build the Application:** Navigate to the project directory and build the application. + + ```bash + cd tailcall + cargo build + ``` + +4. **Start the Server:** To start the server, use the following command: + ```bash + cargo run -- start ./examples/jsonplaceholder.graphql + ``` + Once the server is running, you can access the GraphiQL interface at [http://localhost:8000/graphiql](http://localhost:8000/graphiql). + +## Making Changes + +1. **Create a New Branch:** Always create a new branch for your changes. + + ```bash + git checkout -b feature/your-feature-name + ``` + +2. **Write Clean Code:** Ensure your code is clean, readable, and well-commented. +3. **Follow Rust Best Practices:** Adhere to the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html). +4. **Use Title Case in Job Names:** When adding new CI jobs to `.github/workflows`, please use title case e.g. _Close Stale Issues and PR_. + +## Testing + +1. **Write Tests:** For every new feature or bugfix, ensure that you write appropriate tests. + Structure your tests in the following way: + + ```rust + use pretty_assertions::assert_eq; + fn test_something_important() { + let value = setup_something_using_a_function(); + + let actual = perform_some_operation_on_the_value(value); + let expected = ExpectedValue {foo: 1, bar: 2}; + + assert_eq!(actual, expected); + } + ``` + + - Setup the value using helper methods in tests. + - Create an actual and an expected value. + - Assert the two values in a new line. + - Ensure there is one assertion per test. + +2. **Run Tests:** Before submitting a pull request, ensure all tests pass. + ```bash + cargo test + ``` + +## Programming Style Guidelines + +- When calling functions that do not need to modify values, pass references of those values. +- When calling functions that need to modify values, pass ownership of the values, and ensure they are returned from the function. + +**IMPORTANT:** This programming style may not be suitable for performance-sensitive components or hot code paths. In such cases, prioritize efficiency and optimization strategies to enhance performance. + +## Telemetry + +Tailcall implements high observability standards that by following [OpenTelemetry](https://opentelemetry.io) specification. This implementation relies on the following crates: + +- [rust-opentelemetry](https://docs.rs/opentelemetry/latest/opentelemetry/index.html) and related crates to implement support for collecting and exporting data +- [tracing](https://docs.rs/tracing/latest/tracing/index.html) and [tracing-opentelemetry](https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/index.html) to define logs and traces and thanks to integration with opentelemetry that data is automatically transferred to opentelemetry crates. Such a wrapper for telemetry allows to use well-defined library like tracing that works well for different cases and could be used as simple telemetry system for logging without involving opentelemetry if it's not required + +When implementing any functionality that requires observability consider the following points: + +- Add traces for significant amount of work that represents single operation. This will make it easier to investigate problems and slowdowns later. +- For naming spans refer to the [opentelemetry specs](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span) and make the name as specific as possible but without involving high cardinality for possible values. +- Due to limitations of tracing libraries, span names must be restricted to static strings. This constraint can be addressed by specifying an additional field with the special name `otel.name` (for details, refer to the `tracing-opentelemetry` docs). +- The naming of the attributes should follow the opentelemetry's [semantic convention](https://opentelemetry.io/docs/concepts/semantic-conventions/). Existing constants can be obtained with the [opentelemetry_semantic_conventions](https://docs.rs/opentelemetry-semantic-conventions/latest/opentelemetry_semantic_conventions/index.html) crate. + +## Benchmarks Comparison + +### Criterion Benchmarks + +1. **Important:** Make sure all the commits are done. +2. **Install packages:** Install cargo-criterion rust-script. + ```bash + cargo install cargo-criterion rust-script + ``` +3. **Comparing Benchmarks:** + You need to follow the following steps to compare benchmarks between `main`(Baseline) and your branch. + + ```bash + git checkout main + cargo criterion --message-format=json > main.json + git checkout - + cargo criterion --message-format=json > feature.json + ./scripts/criterion_compare.rs base.json main.json table + + ``` + +4. **Check the Results:** If the benchmarks show more than 10% degradation, the script will exit with an error. Please check "benches/benchmark.md" file to identify the benchmarks that failed and investigate the code changes that might have caused the degradation. + +## Documentation + +1. **Update README:** If your changes necessitate a change in the way users interact with the application, update the README accordingly. +2. **Inline Documentation:** Add inline documentation to your code where necessary. + +## Committing Your Changes + +1. **Atomic Commits:** Make sure each commit is atomic (i.e., it does one thing). This makes it easier to review and revert if necessary. +2. **Commit Message Guidelines:** Write meaningful commit messages. Start with a short summary (50 chars max), followed by a blank line and then a detailed description if needed. + +## Submitting a Pull Request + +1. **Push to Your Fork:** Push your changes to your fork on GitHub. + + ```bash + git push origin feature/your-feature-name + ``` + +2. **Open a Pull Request:** Navigate to the original repository on GitHub and open a pull request against the `main` or `develop` branch. +3. **Describe Your Changes:** In the pull request description, explain the changes you made, the issues they resolve, and any other relevant information. +4. **Wait for Review:** Maintainers will review your pull request. Address any comments or feedback they provide. + +## Spread the Word + +1. **Star the Repository:** If you find this project useful, please give it a star on GitHub. This helps increase its visibility and encourages more people to contribute. +2. **Tweet About Your Contribution:** Share your contributions and experiences with the wider community on Twitter. Use the hashtag `#TailcallContributor` and tag `@tailcallhq` to let us know! + +## Community + +1. **Be Respectful:** Please remember that this is an open-source project and the community is welcoming and respectful to all members. + +## Final Words + +Thank you for contributing! Your efforts help improve the application for everyone. diff --git a/docs/contributors/tests.mdx b/docs/contributors/tests.mdx new file mode 100644 index 0000000000..5b9e2d8948 --- /dev/null +++ b/docs/contributors/tests.mdx @@ -0,0 +1,280 @@ +--- +title: "Test Guidelines" +description: "This guide details Tailcall's Markdown-based snapshot testing framework, explaining its structure, syntax, and testing process. It is designed for developers implementing or optimizing tests in a language-agnostic environment, emphasizing efficient snapshot utilization and maintenance within Tailcall." +sidebar_position: 3 +--- + +A Markdown-based snapshot testing framework in **Tailcall**. + +## Table of contents + +- [Why a new testing framework?](#why-a-new-testing-framework) +- [How does it work?](#how-does-it-work) +- [Structure](#structure) +- [Test syntax](#test-syntax) + - [Header](#header) + - [Annotation](#annotation) + - [Blocks](#blocks) + - [`@server`](#server) + - [`@mock`](#mock) + - [`@env`](#env) + - [`@assert`](#assert) + - [`@file:`](#filefilename) + - [Instruction](#instruction) +- [Test process](#test-process) +- [Snapshots](#snapshots) +- [Maintenance](#maintenance) + +## Why a new testing framework? + +We aimed to create a snapshot testing framework that is language-agnostic, straightforward to write, maintain, and understand. For this reason, we chose a Markdown-based design. This design closely aligns with the usage patterns of Tailcall users. Since Tailcall supports building scalable GraphQL backends without being tied to a specific programming language, it was essential for our testing framework to be similarly language-agnostic. + +## How does it work? + +[execution_spec](https://github.com/tailcallhq/tailcall/blob/main/tests/execution_spec.rs) implements a Markdown-based snapshot testing framework for Tailcall. The framework is designed to test the execution of Tailcall configs, and it is based on the following architecture: +![Test Architecture](../../static/images/contributors/test-arch.png) + +## Structure + +All `execution_spec` tests are located in `tests/execution/`. The results generated by these tests are stored as snapshots in `tests/snapshots/`. + +## Test syntax + +An `execution_spec` test is always a Markdown file with a `.md` extension. These files contain the following parts: + +### Header + +A level 1 heading (`#`) specifying the name of the test, and an optional paragraph after it specifying a description. There must be precisely one header in a test. + +Examples: + +- ```md + # Simple test + ``` +- ```md + # Complex test + + This is a description. + ``` + +### Annotation + +A level 5 heading (`#####`), with the text being one of the following: + +- `##### only` -- If at least one test has the `only` annotation, the runner will run this/these test(s). +- `##### skip` -- If a test has the `skip` annotation, the runner will not run that test. + +These are typically added to tests temporarily, so try not to commit tests with annotations. There must be either zero or one annotations in a test. + +### Blocks + +Blocks are specified along with the codeblocks next to the format of the codeblock (`@`) followed by the block type, and a code block after them. Blocks supply the runner with data, and the runner determines what to do based on the available blocks. example: + +````md + + +```graphql @server +schema { + query: Query +} +``` +```` + +#### `@server:` + +A `@server` block lets you specify a server SDL config. These are expected to be parseable to have a passing test, unless the [`SDL error` instruction](#instruction) is specified, which requires the config parsing to throw an error. There must be at least one `@server` block in a test. + +Every test should have at least one `@server` block. Some blocks (for example, `@assert`) require precisely one `@server` block. Moreover, having precisely one `@server` block in a test ensures that the `client` check will also be performed. + +The `merge` check is always performed using every defined server block. + +When the [`check identity` instruction](#instruction) is specified, the runner will attempt to perform an `identity` check, but since is a "dumb", plain-text check, it requires the `server` block's code to be written in a specific way. + +Example: + +````md + + +```graphql @server +schema { + query: Query +} + +type User { + id: Int + name: String +} + +type Query { + user: User @http(path: "/users/1", baseURL: "http://jsonplaceholder.typicode.com") +} +``` +```` + +#### `@mock:` + +A `@mock` block specifies mocked HTTP endpoints in `YAML`. + +An item of `mock` contains a `request` and a `response`. + +There may be at most one `mock` block in a test. + +Example: + +````md + + +```graphql @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + response: + status: 200 + body: + id: 1 + name: foo +``` +```` + +#### `@env:` + +An `@env` block specifies environment variables in `YAML` that the runner should use in the app context. + +There may be at most one `@env` block in a test. + +Example: + +````md + + +```yml @env +TEST_ID: 1 +``` +```` + +#### `@assert:` + +An `@assert` block specifies HTTP requests that the runner should perform in `YAML`. +It solely contains requests. The response for each request is stored in an `assert_{i}` snapshot. + +There may be at most one `@assert` block in a test. + +Example: + +````md + + +```graphql @assert +- method: POST + url: http://localhost:8080/graphql + body: + query: query { user { name } } +``` +```` + +#### `@file:` + +A `@file` block creates a file in the spec's virtual file system. The [`@server` block](#server) will have exclusive access to files created in this way: the true filesystem is not available to it. + +Every `@file` block has the filename declared in the header. The language of the code block is optional and does not matter. + +Example: + +````md + + +```graphql @file:enum.graphql +schema @server(port: 8080) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +enum Foo { + BAR + BAZ +} + +type Query { + foo: Foo @http(path: "/foo") +} +``` +```` + +### Instruction + +A header (`---`), followed by an instruction: + +```md +--- +expect_validation_error: true +--- +``` + +- This instructs the runner to expect a failure when parsing the `server` block and to compare the result with an `errors` snapshot. This is used when testing for error handling. + +```md +--- +check_identity: true +--- +``` + +- This instructs the runner to run identity checks on `server` blocks. While it would be good to run this on every test, the code of `server` blocks must be written with this instruction mind, therefore it is optional. + +There must be precisely zero or one instruction in a test. + +## Test process + +1. The runner reads all tests, and selects the ones to run based on the following: + - If a path to a test was given in the first command line argument, solely that test will run. + - If one or more tests have an [`only` annotation](#annotation), those tests will run. + - If one or more tests have a [`skip` annotation](#annotation), every test except those will run. + - If none of the above is true, all tests will run. +1. The runner evaluates every test. + 1. If the test has an [`SDL error` instruction](#instruction), the runner does the following: + 1. Reads and parses the config, taking note of the validation errors. + 1. **If no validation errors occurred, the runner throws an error.** (`SDL error` is a requirement, not a try-catch.) + 1. Compares the encountered errors to the `errors` snapshot. + 1. If the snapshot doesn't match the encountered errors, the runner generates a new snapshot and throws an error. + 1. Ends the test run, and starts evaluating the next test. (All other actions would require a parseable `@server` block.) + 1. The runner parses every `@server` block. + 1. Parses the block and checks for errors. + 1. If the test has a [`check identity` instruction](#instruction), the runner converts the parsed block to SDL again, and checks if the two strings are the same. If they're not, the runner throws an error. + 1. The runner performs a `merge` check: + 1. Attempts to merge all [`@server` blocks](#server), resulting in a merged config. (If there is a single [`@server` block](#server), the runner will merge it with the default config.) + 1. Compares the merged config to the `merged` snapshot. + 1. If the snapshot doesn't match the merged config, the runner generates a new snapshot and throws an error. + 1. If there is precisely one [`@server` block](#server), the runner performs a `client` check: + 1. Generates the client schema of the `server` block. + 1. Compares it to the `client` SDL snapshot. + 1. If the snapshot doesn't match the generated schema, the runner generates a new snapshot and throws an error. + 1. If the test has an [`@assert` block](#assert), the runner performs `assert` checks: + 1. If there is a [`@mock` block](#mock), the runner sets up the mock HTTP client based on it. + 1. If there is at least one [`@file` block](#filefilename), the runner sets up the mock filesystem based on them. + 1. If there is an [`@env` block](#env), the runner uses it for the app context. + 1. Creates an app context based on the [`@server` block](#server). + 1. For each assertion in the block (0-based index `i`), the runner does the following: + 1. Runs the HTTP request on the app context. + 1. Compares the HTTP response to the `assert_{i}` snapshot. + 1. If the snapshot doesn't match the response, the runner generates a new snapshot and throws an error. + +## Snapshots + +`execution_spec` uses the [Insta](https://insta.rs) snapshot engine. Snapshots are automatically generated with a `.new` suffix if there is no pre-existing snapshot, or if the compared data didn't match the existing snapshot. + +Instead of writing result cases in tests and updating them when behaviour changes, a snapshot-based testing workflow relies on auto-generation. Whenever a `.new` snapshot is generated, it means one of the following: + +- Your code made an unexpected breaking change, and you need to fix it. +- Your code made an expected breaking change, and you need to accept the new snapshot. + +You need to determine which one is the case, and take action accordingly. + +Usage of [cargo-insta](https://insta.rs/docs/cli/) is recommended: + +```bash +cargo insta test --review +``` + +This will regenerate all snapshots without interrupting the test every time there's a diff, and it will also open the snapshot review interface, so that you can accept or reject `.new` snapshots. + +## Maintenance + +1. To clean unused snapshots, run `cargo insta test --delete-unreferenced-snapshots`. diff --git a/static/images/contributors/test-arch.png b/static/images/contributors/test-arch.png new file mode 100644 index 0000000000..d8df06b107 Binary files /dev/null and b/static/images/contributors/test-arch.png differ