Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Run linter
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.12.2
version: v2.13.1

- name: Scan for secrets
# gitleaks/gitleaks-action requires a paid license for GitHub
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CodeQL

# Replaces CodeQL default setup. Default setup pins its own Go toolchain and
# runs the extractor with GOTOOLCHAIN=local, so it cannot build a module whose
# go directive is newer than the bundle. Driving it here lets setup-go install
# the toolchain go.mod asks for, so a Go bump never blocks code scanning.

on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
# Weekly, Tuesday 05:44 UTC. Off the top of the hour and off the Security
# workflow's Monday slot.
- cron: '44 5 * * 2'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
# Uploading results to the code scanning API.
security-events: write
strategy:
fail-fast: false
matrix:
include:
- language: go
build-mode: manual
- language: actions
build-mode: none
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Go
if: matrix.language == 'go'
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod

- name: Initialize CodeQL
uses: github/codeql-action/init@c16c0f3f2812ec4bb3750a5ed64873fe2ce0fbef # codeql-bundle-v2.26.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# Matches the query suite and threat model the previous default
# setup ran with.
queries: security-extended

- name: Build
if: matrix.build-mode == 'manual'
run: go build ./...

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@c16c0f3f2812ec4bb3750a5ed64873fe2ce0fbef # codeql-bundle-v2.26.3
with:
category: /language:${{ matrix.language }}
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [1.7.0] - 2026-08-24

### Changed

- Go toolchain moves to 1.27.0, from the 1.26 line. The `go` directive in `go.mod`
is what every workflow feeds to `setup-go`, so this sets the language version and
the standard library CI builds against. The Dockerfile builder image moves to
`golang:1.27.0` to match.
- golangci-lint moves from v2.12.2 to v2.13.1. Its `modernize` linter flagged twelve
issues in the test suite, all now fixed: embedded fields written through the
promoted-field shorthand in composite literals, and one `errors.As` call replaced
with `errors.AsType`, which the 1.27 standard library adds.
- Every GitHub Action was checked against its latest release. All were already
pinned to the newest version by commit SHA, so none needed to move.
- CodeQL moves from default setup to a workflow in the repository. Default setup
pins its own Go toolchain and runs the extractor with `GOTOOLCHAIN=local`, so it
could not build a module whose `go` directive asks for 1.27.0. The workflow runs
`setup-go` against `go.mod` first, so a future Go bump will not block code
scanning. Same languages, same `security-extended` query suite as before.

### Known limits

- `errors.AsType` means the test suite no longer builds on Go 1.26.

## [1.6.1] - 2026-08-24

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG GIT_SHA="no-sha"
FROM --platform=$BUILDPLATFORM golang:1.26.7 AS build-env
FROM --platform=$BUILDPLATFORM golang:1.27.0 AS build-env
WORKDIR /app
ARG GIT_SHA
ARG TARGETARCH
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/yottabytesolutions/meterlogger

go 1.26.7
go 1.27.0

require (
github.com/ClickHouse/clickhouse-go/v2 v2.48.0
Expand Down
12 changes: 7 additions & 5 deletions internal/adapters/sink/mqtt/writers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,10 @@ func TestDucoWriter_Nodes(t *testing.T) {
ctx := context.Background()

// RF sensor node.
rf := domain.DucoRFSensorStatus{BaseDucoNodeStatus: ducoBase(), Temp: 21.5, Co2: 600, Rh: 55}
rf.Node = 3
rf.DevType = "SENSO"
rfBase := ducoBase()
rfBase.Node = 3
rfBase.DevType = "SENSO"
rf := domain.DucoRFSensorStatus{BaseDucoNodeStatus: rfBase, Temp: 21.5, Co2: 600, Rh: 55}
if err := w.StoreNodeData(ctx, rf); err != nil {
t.Fatalf("StoreNodeData rf: %v", err)
}
Expand All @@ -472,8 +473,9 @@ func TestDucoWriter_Nodes(t *testing.T) {
}

// Box node and valve node land on their own topics.
boxNode := domain.DucoNodeBoxStatus{BaseDucoNodeStatus: ducoBase(), Trgt: 30, Actl: 20, Temp: 20, Co2: 450, Rh: 40}
boxNode.Node = 1
boxBase := ducoBase()
boxBase.Node = 1
boxNode := domain.DucoNodeBoxStatus{BaseDucoNodeStatus: boxBase, Trgt: 30, Actl: 20, Temp: 20, Co2: 450, Rh: 40}
if err := w.StoreNodeData(ctx, boxNode); err != nil {
t.Fatalf("StoreNodeData box node: %v", err)
}
Expand Down
36 changes: 15 additions & 21 deletions internal/adapters/sink/qdb/qdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,14 +664,12 @@ func TestDucoQuestDBRepository_StoreNodeData_RFSensor(t *testing.T) {
client, sender := newTestDBClient()
repo := NewDucoQuestDBRepository(client, "ventilation", testLogger())
node := domain.DucoRFSensorStatus{
BaseDucoNodeStatus: domain.BaseDucoNodeStatus{
Node: 3,
DevType: "UCCO2",
Location: "living room",
},
Co2: 800.0,
Temp: 21.5,
Rh: 55.0,
Node: 3,
DevType: "UCCO2",
Location: "living room",
Co2: 800.0,
Temp: 21.5,
Rh: 55.0,
}
if err := repo.StoreNodeData(context.Background(), node); err != nil {
t.Fatalf("StoreNodeData(RFSensor) unexpected error: %v", err)
Expand All @@ -691,13 +689,11 @@ func TestDucoQuestDBRepository_StoreNodeData_BoxNode(t *testing.T) {
client, sender := newTestDBClient()
repo := NewDucoQuestDBRepository(client, "ventilation", testLogger())
node := domain.DucoNodeBoxStatus{
BaseDucoNodeStatus: domain.BaseDucoNodeStatus{
Node: 1,
DevType: "BOX",
},
Trgt: 100,
Actl: 80,
Co2: 600.0,
Node: 1,
DevType: "BOX",
Trgt: 100,
Actl: 80,
Co2: 600.0,
}
if err := repo.StoreNodeData(context.Background(), node); err != nil {
t.Fatalf("StoreNodeData(BoxNode) unexpected error: %v", err)
Expand All @@ -716,12 +712,10 @@ func TestDucoQuestDBRepository_StoreNodeData_Valve(t *testing.T) {
client, sender := newTestDBClient()
repo := NewDucoQuestDBRepository(client, "ventilation", testLogger())
node := domain.DucoNodeBoxValveStatus{
BaseDucoNodeStatus: domain.BaseDucoNodeStatus{
Node: 2,
DevType: "VLV",
},
Trgt: 50,
Actl: 45,
Node: 2,
DevType: "VLV",
Trgt: 50,
Actl: 45,
}
if err := repo.StoreNodeData(context.Background(), node); err != nil {
t.Fatalf("StoreNodeData(Valve) unexpected error: %v", err)
Expand Down
30 changes: 15 additions & 15 deletions internal/adapters/source/ducobox/ducobox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func testLogger() *slog.Logger {

func TestParseDucoNodeStatus_Box(t *testing.T) {
node := nodeBoxStatusDTO{
baseNodeStatusDTO: baseNodeStatusDTO{Node: 1, DevType: devTypeBox},
Trgt: 100,
Actl: 80,
Rh: 55.5,
Node: 1, DevType: devTypeBox,
Trgt: 100,
Actl: 80,
Rh: 55.5,
}
data, err := json.Marshal(node)
if err != nil {
Expand Down Expand Up @@ -63,9 +63,9 @@ func TestParseDucoNodeStatus_BoxLowercase(t *testing.T) {

func TestParseDucoNodeStatus_VLV(t *testing.T) {
node := nodeBoxValveStatusDTO{
baseNodeStatusDTO: baseNodeStatusDTO{Node: 2, DevType: devTypeValve},
Trgt: 50,
Actl: 45,
Node: 2, DevType: devTypeValve,
Trgt: 50,
Actl: 45,
}
data, err := json.Marshal(node)
if err != nil {
Expand All @@ -88,9 +88,9 @@ func TestParseDucoNodeStatus_VLV(t *testing.T) {

func TestParseDucoNodeStatus_UCCO2(t *testing.T) {
node := rfSensorStatusDTO{
baseNodeStatusDTO: baseNodeStatusDTO{Node: 3, DevType: devTypeUCCO2},
Co2: 800.0,
Rh: 0,
Node: 3, DevType: devTypeUCCO2,
Co2: 800.0,
Rh: 0,
}
data, err := json.Marshal(node)
if err != nil {
Expand All @@ -113,9 +113,9 @@ func TestParseDucoNodeStatus_UCCO2(t *testing.T) {

func TestParseDucoNodeStatus_UCRH(t *testing.T) {
node := rfSensorStatusDTO{
baseNodeStatusDTO: baseNodeStatusDTO{Node: 4, DevType: devTypeUCRH},
Co2: 0,
Rh: 65.0,
Node: 4, DevType: devTypeUCRH,
Co2: 0,
Rh: 65.0,
}
data, err := json.Marshal(node)
if err != nil {
Expand Down Expand Up @@ -259,8 +259,8 @@ func TestDucoReader_ReadBoxStatus_InvalidJSON(t *testing.T) {

func TestDucoReader_ReadNodeStatus_Success(t *testing.T) {
node := nodeBoxStatusDTO{
baseNodeStatusDTO: baseNodeStatusDTO{Node: 1, DevType: devTypeBox},
Trgt: 100,
Node: 1, DevType: devTypeBox,
Trgt: 100,
}
body, _ := json.Marshal(node)

Expand Down
3 changes: 1 addition & 2 deletions internal/adapters/source/enphase/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,7 @@ func TestCloudClient_RejectsSelfSignedTLS(t *testing.T) {
_ = resp.Body.Close()
t.Fatal("cloud client should reject self-signed TLS")
}
var certErr *tls.CertificateVerificationError
if !errors.As(err, &certErr) {
if _, ok := errors.AsType[*tls.CertificateVerificationError](err); !ok {
t.Errorf("expected certificate verification error, got: %v", err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ func TestDucoLoggingService_Start_Flushes(t *testing.T) {
func TestDucoLoggingService_Start_WithNodes(t *testing.T) {
boxStatus := domain.DucoBoxStatus{}
nodeData := domain.DucoNodeBoxStatus{
BaseDucoNodeStatus: domain.BaseDucoNodeStatus{Node: 1, DevType: "BOX"},
Node: 1, DevType: "BOX",
}
reader := &mockDucoReader{
boxStatus: boxStatus,
Expand Down
Loading