Compare commits

...

10 Commits

Author SHA1 Message Date
Erich Styger
75b86eee16 Upload New File 2025-07-09 14:44:44 +02:00
286b05ad57 Merge branch 'feat/test-env'
add test environment

See merge request team-raclette/project-softweng!23
2025-06-24 22:07:33 +02:00
35225ada3c fix(pico-sensor): path relative to root of project as GitLab need it
GitLab need a path from the root of the project even it's in a sub-folder

Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:38:54 +02:00
b88f24385d doc: add test environment section to README
Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:26:09 +02:00
9c5840f400 doc: update contributor roles in README
Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:26:09 +02:00
6e62e28be6 feat(gateway): add test routes
Return 100 last values to be sure that test have some values
Need to be refactor to merge both function. Now it's clearly a mess with duplicate code.

Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:26:09 +02:00
caa29f2d25 feat(test): add test environnement
remove useless environnement variable

Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:26:07 +02:00
c103e8c6b4 refactor: tiny polish
remove useless ports
add space

Signed-off-by: Klagarge <remi@heredero.ch>
2025-06-24 21:25:45 +02:00
Yann Sierro
25c999d143 Merge branch '37-fix-ci-e2e'
test(webapp): fix CI test E2E

Closes #37

See merge request team-raclette/project-softweng!22
2025-06-24 19:16:49 +00:00
fastium
b03368aa6f test(webapp): update documentation
- Change the description about the environment test
2025-06-24 21:13:51 +02:00
10 changed files with 180 additions and 18 deletions

View File

@@ -8,6 +8,4 @@ MQTT_USERNAME=
MQTT_PASSWORD=
REST_USERNAME=
REST_PASSWORD=
REST_URL=
REST_PAGE=
CYPRESS_TEST_URL=

View File

@@ -1,5 +1,6 @@
stages:
- gateway-build
- web-app-build
- web-app-tests

View File

@@ -0,0 +1,42 @@
# Grading Embedded Part Group Project 4
- Normal Submission
- Repository: https://gitlab.forge.hefr.ch/groups/team-raclette
## Grading Criterias
For the embedded part of the group project, the following applies:
- Maximum `3 Points`: A **CI/CD** pipeline has to be set and run to build the **embedded sensor**
application. It is expected that the application builds on GitLab with a docker image and
container, and that the build artifacts (application binaries) are delivered as files.
- Maximum `4 Points`: An **automated embedded application testing** has to be set up. During
the module, an example framework with some tests is provided. It is expected that this
framework and tests have been extended with additional on-target tests. It is not expected
that the tests have to be run on-target with the pipeline on GitLab (as there are limitations,
see course content). But it is expected that the on-target testes are running locally on a
machine with a docker image, and that test output and artifacts about successfully running
the tests are provided.
## CI/CD Embedded Sensor
- sensor part in https://gitlab.forge.hefr.ch/team-raclette/project-softweng/-/tree/main/pico-sensor?ref_type=heads
- extended command line for MQTT topic names, very good!
- pico-sensor-docker-build failed
*Grade:* `2 points`
## Automated Embedded Application Testing
- documented testing with local tests
- extended tests with own checks
- addede MQTT test cases
- prepared test setup in https://gitlab.forge.hefr.ch/team-raclette/project-softweng/-/blob/main/pico-sensor/.gitlab-ci.yml?ref_type=heads
- documentation about running local tests, but no result artefacts
*Grade:* `3 points`
## General feedback
- good top level README.md describing the approach
- good description with https://gitlab.forge.hefr.ch/team-raclette/project-softweng/-/blob/main/pico-sensor/README.md
- instaead of using McuMinINI (good that you found this!), the settings can be stored with the usual command line commands
- good structure with local .yml files
- could have stored the test result .xml in the repository

View File

@@ -79,14 +79,23 @@ Credentials are available [on request](mailto:remi@heredero.ch).
Details in [pico-sensor/README.md](pico-sensor/README.md).
## Test environment
[(Back to top)](#table-of-contents)
A partial test environment is deployed on the server. <br>
Web app has a second container with the same code but made a GET request on `/test` instead of `/raclette` for the
development environment. The gateway include a route to `/test` that return the 100 last measurement instead of the
last 24h. Has everything else is still a development environment, the database is the same for the test and
development environment.
# Authors
[(Back to top)](#table-of-contents)
* [Sylvan Arnold](https://github.com/Sylvan22)
* [Rémi Heredero](https://github.com/Klagarge)
* [Yann Sierro](https://github.com/Fastium)
* [Sylvan Arnold](https://github.com/Sylvan22) - Mainly pico-sensor part
* [Rémi Heredero](https://github.com/Klagarge) - Mainly gateway and docker part
* [Yann Sierro](https://github.com/Fastium) - Mainly web-app part

View File

@@ -22,8 +22,6 @@ services:
image: registry.forge.hefr.ch/team-raclette/project-softweng/gateway:latest
container_name: gateway
restart: unless-stopped
ports:
- "8080:8080"
environment:
- INFLUXDB_TOKEN=$INFLUXDB_TOKEN
- INFLUXDB_ORG=$INFLUXDB_ORG
@@ -105,15 +103,11 @@ services:
image: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest
container_name: web-app
restart: unless-stopped
ports:
- "8080:8080"
environment:
- VUE_APP_REST_USER=$REST_USERNAME
- VUE_APP_REST_PASSWORD=$REST_PASSWORD
- VUE_APP_REST_URL=$REST_URL
- VUE_APP_REST_PAGE=$REST_PAGE
- VUE_APP_REST_URL=rest.mse.kb28.ch
- VUE_APP_REST_PAGE=raclette
labels:
- "traefik.enable=true"
- "traefik.http.routers.web-app-http.entrypoints=http"
@@ -125,3 +119,24 @@ services:
- "traefik.http.services.web-app-https.loadbalancer.server.port=8080"
- "com.centurylinklabs.watchtower.enable=true"
web-app-test:
image: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest
container_name: web-app-test
restart: unless-stopped
environment:
- VUE_APP_REST_USER=$REST_USERNAME
- VUE_APP_REST_PASSWORD=$REST_PASSWORD
- VUE_APP_REST_URL=rest.mse.kb28.ch
- VUE_APP_REST_PAGE=test
labels:
- "traefik.enable=true"
- "traefik.http.routers.web-app-test-http.entrypoints=http"
- "traefik.http.routers.web-app-test-http.rule=Host(`app-test.mse.kb28.ch`)"
- "traefik.http.middlewares.web-app-test-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.web-app-test-https.entrypoints=https"
- "traefik.http.routers.web-app-test-https.rule=Host(`app-test.mse.kb28.ch`)"
- "traefik.http.routers.web-app-test-https.tls.certResolver=letsencrypt"
- "traefik.http.services.web-app-test-https.loadbalancer.server.port=8080"
- "com.centurylinklabs.watchtower.enable=true"

View File

@@ -22,6 +22,7 @@ type Gateway struct {
mqtt paho.Client
rest *gin.Engine
restGroup *gin.RouterGroup
testGroup *gin.RouterGroup
accounts gin.Accounts
influx influxdb2.Client
influxApi api.WriteAPIBlocking
@@ -100,6 +101,7 @@ func (gh *Gateway) createRestGateway() {
// Create a new router group with basic authentication for /raclette
gh.restGroup = gh.rest.Group("/raclette", gin.BasicAuth(gh.accounts))
gh.testGroup = gh.rest.Group("/test", gin.BasicAuth(gh.accounts))
// Define the route for the publish command
gh.restGroup.POST("", func(c *gin.Context) {
@@ -129,6 +131,14 @@ func (gh *Gateway) createRestGateway() {
c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()})
}
})
// Define the route for the publish command (GET)
gh.testGroup.GET("", func(c *gin.Context) {
ret := gh.requestInfluxTest(c)
if ret != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()})
}
})
}
// createMQTTGateway initializes the MQTT client and sets up subscriptions.

View File

@@ -68,6 +68,7 @@ func (gh *Gateway) requestInflux(c *gin.Context) error {
|> filter(fn: (r) => r["user"] == %q)
|> filter(fn: (r) => r["room"] == %q)
|> filter(fn: (r) => r["device"] == %q)
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
|> sort(columns: ["_time"], desc: true)
`, INFLUXDB_BUCKET, MEASUREMENT_NAME, user, room, device)
results, err := queryAPI.Query(context.Background(), query)
@@ -101,3 +102,83 @@ func (gh *Gateway) requestInflux(c *gin.Context) error {
c.JSON(http.StatusOK, values)
return nil
}
func (gh *Gateway) requestInfluxTest(c *gin.Context) error {
// Get the user from the authenticated context
//userId := c.MustGet(gin.AuthUserKey).(string)
// Get room and device from the query parameters
user, ret := c.GetQuery("user")
if !ret {
return errors.New("no user found")
}
room, ret := c.GetQuery("room")
if !ret {
return errors.New(`no room found`)
}
device, ret := c.GetQuery("device")
if !ret {
return errors.New(`no device found`)
}
// Get env variables and set default values if not set
INFLUXDB_ORG, ok := os.LookupEnv("INFLUXDB_ORG")
if !ok {
log.Error("INFLUXDB_ORG not set, using default value: raclette")
INFLUXDB_ORG = "raclette"
}
INFLUXDB_BUCKET, ok := os.LookupEnv("INFLUXDB_BUCKET")
if !ok {
log.Error("INFLUXDB_BUCKET not set, using default value: raclette")
INFLUXDB_BUCKET = "raclette"
}
queryAPI := gh.influx.QueryAPI(INFLUXDB_ORG)
MEASUREMENT_NAME, ok := os.LookupEnv("MEASUREMENT_NAME")
if !ok {
log.Error("MEASUREMENT_NAME not set, using default value: softweng")
MEASUREMENT_NAME = "THC"
}
// The Flux query uses a large range (-1000d) and aggregates the latest values.
// This ensures we always get the most recent data, even if the database contains old entries.
query := fmt.Sprintf(`from(bucket: %q)
|> range(start: -1000d)
|> filter(fn: (r) => r["_measurement"] == %q)
|> filter(fn: (r) => r["user"] == %q)
|> filter(fn: (r) => r["room"] == %q)
|> filter(fn: (r) => r["device"] == %q)
|> sort(columns: ["_time"], desc: true)
|> limit(n: 100)
`, INFLUXDB_BUCKET, MEASUREMENT_NAME, user, room, device)
results, err := queryAPI.Query(context.Background(), query)
if err != nil {
log.Fatal(err)
}
type measurement = map[string]interface{}
var values []measurement
for results.Next() {
m := measurement{}
record := results.Record()
field := record.Field()
value := record.Value()
m["time"] = record.Time()
m["value"] = value.(float64)
if field == "temperature" {
m["type"] = "temperature"
}
if field == "humidity" {
m["type"] = "humidity"
}
values = append(values, m)
fmt.Println(results.Record())
}
if err := results.Err(); err != nil {
log.Fatal(err)
}
c.JSON(http.StatusOK, values)
return nil
}

View File

@@ -70,7 +70,7 @@ RUN \
ENV PICO_SDK_PATH=/apps/pico-sdk/
# Patch the SDK for semihosting file I/O (needed for gcov)
COPY newlib_interface.c.patched ${PICO_SDK_PATH}/src/rp2_common/pico_clib_interface/newlib_interface.c
COPY ./pico-sensor/newlib_interface.c.patched ${PICO_SDK_PATH}/src/rp2_common/pico_clib_interface/newlib_interface.c
# Build picotool so we don't have to build it for the project
RUN \

View File

@@ -65,7 +65,10 @@ npm run build
<p align="center"> ![Class Diagram](docs/class-diagramm-web-app.svg)
### Links
## Tests
Details in [web-app/test.md](web-app/test.md)
# Links
See [Configuration Reference](https://cli.vuejs.org/config/).\
See [Vue.js](https://vuejs.org/guide/introduction.html).\
See [chartjs](https://www.chartjs.org/docs/latest/).\

View File

@@ -65,9 +65,6 @@ npx cypress open
- Tests fetching timeseries data with a valid user-room-device selection, ensuring the main chart is displayed and no "no data" message appears.
- Tests fetching timeseries data with an invalid user-room-device selection (e.g., changing the user to "Sylvan"), ensuring the main chart is not displayed and the "no data" message is shown.
#### Important Notes
The E2E tests use a web-app development image deployed on https://app.mse.kb28.ch/ for the moment. Because there is a problem with the service image in the CI. The environment variables cannot be set correctly to the job's service. An issue is opened to fix this problem: [#37].
### Unit Tests
- **Serie Component**:
@@ -76,3 +73,9 @@ The E2E tests use a web-app development image deployed on https://app.mse.kb28.c
- Ensures the label "Unknown" is returned for unknown types.
- Verifies that temperature and humidity data are formatted correctly by `getSerie()`, including label, color, and data structure.
- Ensures unknown types are handled appropriately in `getSerie()`, defaulting to the correct color and axis.
## Test in CI
All test are covered by the CI pipeline.
The unit tests are run on every commit and doesn't require any specific resources.
The E2E tests are run too on every commit, but they require a test URL to be set in the environment variable `CYPRESS_TEST_URL` which provide an access to a test instance of the web-app. Actually, these tests are running on the test development instance at `https://app-dev.mse.kb28.ch/`.