Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/e2e-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#
# This file is part of Edgehog.
#
# Copyright 2025 SECO Mind Srl
#
# Licensed 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.
#
# SPDX-License-Identifier: Apache-2.0
#

name: End to end test

on:
# Run when pushing to stable branches
push:
paths:
- "backend/**"
- "tools/e2e_test/**"
- ".tool-versions"
- ".github/workflows/e2e-test.yaml"
branches:
- "main"
- "release-*"
# Run on pull requests matching apps
pull_request:
paths:
- "backend/**"
- "tools/e2e_test/**"
- ".tool-versions"
- ".github/workflows/e2e-test.yaml"

defaults:
run:
shell: bash
working-directory: tools/e2e_test
env:
EDGEHOG_TEST_TENANT: "test"
EDGEHOG_TEST_HOSTNAME: "api.edgehog.localhost"
EDGEHOG_TEST_BEARER: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlX3RnYSI6IioiLCJpYXQiOjE3Mzg5NDgzODh9.TTiXYs1LucAnS_6RGp7pWg-S30NSt7eqL7lU8BzT5BWlHctk7NYZwC6lftA6WeEb1HKEJfPoUqWeOeZ6oYA0AA"

jobs:
e2e-test:
name: End to end
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
show-progress: false
- name: Install gleam and BEAM
uses: erlef/setup-beam@v1
with:
otp-version: "28"
gleam-version: "1.13.0"
rebar3-version: "3"
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Setup `just`
uses: extractions/setup-just@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install astartectl
uses: jaxxstorm/action-install-gh-release@v1.10.0
with:
repo: astarte-platform/astartectl
- name: Provision a tenant
run: just provision-tenant
- name: Run e2e test suite
run: |
gleam run
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ nodejs 24.11.0
postgres 18.0
dprint 0.50.2
typos 1.40.0
gleam 1.13.0
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ defmodule EdgehogWeb.Schema.Mutation.DeleteSystemModelTest do

result = delete_system_model_mutation(tenant: tenant, id: id)

assert %{fields: [:id], message: "could not be found"} = extract_error!(result)
assert %{fields: [:id], message: "could not be found"} = extract_error!(result) |> dbg()
end
end

Expand Down
4 changes: 4 additions & 0 deletions tools/e2e_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump
3 changes: 3 additions & 0 deletions tools/e2e_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# E2E edgehog test suite

This test suite mocks a client making graphql queries to the edgehog backend.
28 changes: 28 additions & 0 deletions tools/e2e_test/gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name = "e2e_test"
version = "1.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "", repo = "" }
# links = [{ title = "Website", href = "" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.

[dependencies]
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
gleam_erlang = ">= 1.3.0 and < 2.0.0"
envoy = ">= 1.0.2 and < 2.0.0"
gleam_http = ">= 4.3.0 and < 5.0.0"
gleam_json = ">= 3.1.0 and < 4.0.0"
gleamql = { git= "git@github.com:lusergit/gleamql.git", ref = "push-yupwzqynussm" }
gleam_httpc = ">= 5.0.0 and < 6.0.0"
hackney = ">= 1.25.0 and < 2.0.0"
gleam_hackney = ">= 1.3.2 and < 2.0.0"
logging = ">= 1.3.0 and < 2.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
41 changes: 41 additions & 0 deletions tools/e2e_test/manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "certifi", version = "2.15.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "B147ED22CE71D72EAFDAD94F055165C1C182F61A2FF49DF28BCC71D1D5B94A60" },
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
{ name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" },
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
{ name = "gleam_hackney", version = "1.3.2", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "CF6B627BC3E3726D14D220C30ACE8EF32433F19C33CE96BBF70C2068DFF04ACD" },
{ name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" },
{ name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" },
{ name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" },
{ name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
{ name = "gleamql", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib", "glint"], source = "git", repo = "git@github.com:lusergit/gleamql.git", commit = "7d51def0fe1c783804df7ed170d08ce477aed18c" },
{ name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" },
{ name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" },
{ name = "hackney", version = "1.25.0", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "7209BFD75FD1F42467211FF8F59EA74D6F2A9E81CBCEE95A56711EE79FD6B1D4" },
{ name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
{ name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
{ name = "mimerl", version = "1.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "13AF15F9F68C65884ECCA3A3891D50A7B57D82152792F3E19D88650AA126B144" },
{ name = "parse_trans", version = "3.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A" },
{ name = "snag", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "274F41D6C3ECF99F7686FDCE54183333E41D2C1CA5A3A673F9A8B2C7A4401077" },
{ name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" },
{ name = "unicode_util_compat", version = "0.7.1", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642" },
]

[requirements]
envoy = { version = ">= 1.0.2 and < 2.0.0" }
gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" }
gleam_hackney = { version = ">= 1.3.2 and < 2.0.0" }
gleam_http = { version = ">= 4.3.0 and < 5.0.0" }
gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" }
gleam_json = { version = ">= 3.1.0 and < 4.0.0" }
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
gleamql = { git = "git@github.com:lusergit/gleamql.git", ref = "push-yupwzqynussm" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
hackney = { version = ">= 1.25.0 and < 2.0.0" }
logging = { version = ">= 1.3.0 and < 2.0.0" }
10 changes: 10 additions & 0 deletions tools/e2e_test/src/e2e_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import gleam/dict
import suite/errors
import tests/image_credentials

pub fn main() -> Result(Nil, errors.ErrorSet) {
dict.new()
|> image_credentials.run_test()
|> errors.print()
|> Ok
}
12 changes: 12 additions & 0 deletions tools/e2e_test/src/suite/connection.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import gleam/http
import gleamql
import suite/env

pub fn new() -> gleamql.Request(t) {
gleamql.new()
|> gleamql.set_host(env.api_endpoint())
|> gleamql.set_path(env.graphql_path())
|> gleamql.set_header("Authorization", "Bearer " <> env.admin_jwt())
|> gleamql.set_default_content_type_header()
|> gleamql.set_scheme(http.Http)
}
5 changes: 5 additions & 0 deletions tools/e2e_test/src/suite/context.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import gleamql

pub type Context(t) {
Context(connection: gleamql.Request(t), errors: List(gleamql.GraphQLError))
}
30 changes: 30 additions & 0 deletions tools/e2e_test/src/suite/env.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import envoy

pub fn api_endpoint() -> String {
let endpoint = envoy.get("TEST_EDGEHOG_API_ENDPOINT")

case endpoint {
Ok(endpoint) -> endpoint
Error(_) -> "api.edgehog.localhost"
}
}

pub fn graphql_path() -> String {
let path = envoy.get("TEST_EDGEHOG_TENANT")

case path {
Ok(path) -> path
Error(_) -> "/tenants/test/api"
}
}

pub fn admin_jwt() -> String {
let jwt = envoy.get("TEST_EDGEHOG_JWT")

case jwt {
Ok(jwt) -> jwt
// defaults to the jwt stored in "backend/priv/repo/seeds/keys/tenant_jwt.txt"
Error(_) ->
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJlX3RnYSI6IioiLCJpYXQiOjE3Mzg5NDgzODh9.TTiXYs1LucAnS_6RGp7pWg-S30NSt7eqL7lU8BzT5BWlHctk7NYZwC6lftA6WeEb1HKEJfPoUqWeOeZ6oYA0AA"
}
}
29 changes: 29 additions & 0 deletions tools/e2e_test/src/suite/errors.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import gleam/dict.{type Dict}
import gleam/int
import gleam/io
import gleam/list
import gleamql.{type GraphQLError}

pub type ErrorSet =
Dict(String, List(GraphQLError))

pub fn print(errors: ErrorSet) {
dict.each(errors, print_errors)
}

pub fn print_errors(set_name: String, errors: List(GraphQLError)) {
io.println(" ### Errors in " <> set_name <> " ### ")
list.each(errors, print_error)
}

pub fn print_error(error: GraphQLError) {
case error {
gleamql.ErrorMessage(message) ->
io.println_error("Error while resolving a call: " <> message)
gleamql.UnexpectedStatus(status) ->
io.println_error("Unexpected status: " <> int.to_string(status))
gleamql.UnknownError -> io.println_error("Unknown error!")
gleamql.UnrecognisedResponse(response) ->
io.println_error("Unexpected response: " <> response)
}
}
9 changes: 9 additions & 0 deletions tools/e2e_test/src/suite/logging.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import gleam/io

pub fn pass(test_name: String) -> Nil {
io.println("--- PASS " <> test_name <> " ---")
}

pub fn fail(test_name: String) -> Nil {
io.println("xxx FAIL " <> test_name <> " xxx")
}
15 changes: 15 additions & 0 deletions tools/e2e_test/src/suite/types/request.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import gleam/dynamic/decode.{type Decoder}
import suite/types/request_data.{type Data}
import suite/types/request_error.{type Error, decode_error}

pub type Request(t) {
Request(data: Data(t), errors: List(Error))
}

pub fn decode_request(subfield_decoder: Decoder(t)) -> Decoder(Request(t)) {
let data_decoder = request_data.decode_data(subfield_decoder)

use data <- decode.field("data", data_decoder)
use errors <- decode.field("errors", decode.list(decode_error()))
decode.success(Request(data: data, errors: errors))
}
10 changes: 10 additions & 0 deletions tools/e2e_test/src/suite/types/request_data.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import gleam/dynamic/decode.{type Decoder}

pub type Data(t) {
Data(data: t)
}

pub fn decode_data(subfield_decoder: Decoder(t)) -> Decoder(Data(t)) {
use data <- decode.field("data", subfield_decoder)
decode.success(Data(data: data))
}
33 changes: 33 additions & 0 deletions tools/e2e_test/src/suite/types/request_error.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import gleam/dict
import gleam/dynamic/decode.{type Decoder}

pub type Error {
Error(
code: String,
message: String,
path: List(String),
fields: List(String),
vars: dict.Dict(String, String),
locations: List(dict.Dict(String, Int)),
)
}

pub fn decode_error() -> Decoder(Error) {
use code <- decode.field("code", decode.string)
use message <- decode.field("message", decode.string)
use path <- decode.field("path", decode.list(decode.string))
use fields <- decode.field("fields", decode.list(decode.string))
use vars <- decode.field("vars", decode.dict(decode.string, decode.string))
use locations <- decode.field(
"locations",
decode.list(decode.dict(decode.string, decode.int)),
)
decode.success(Error(
code: code,
message: message,
path: path,
fields: fields,
vars: vars,
locations: locations,
))
}
10 changes: 10 additions & 0 deletions tools/e2e_test/src/suite/types/request_result.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import gleam/dynamic/decode.{type Decoder}

pub type Result(t) {
Result(result: t)
}

pub fn decode_result(subfield_decoder: Decoder(t)) -> Decoder(Result(t)) {
use result <- decode.field("result", subfield_decoder)
decode.success(Result(result: result))
}
12 changes: 12 additions & 0 deletions tools/e2e_test/src/tests/image_credentials.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import gleam/dict
import gleamql
import suite/errors.{type ErrorSet}
import tests/image_credentials/create_credentials

pub fn run_test(init_errors: ErrorSet) -> ErrorSet {
let errors: List(gleamql.GraphQLError) =
[]
|> create_credentials.create_credentials()

dict.insert(init_errors, "ImageCredentials", errors)
}
Loading
Loading