diff --git a/.air.toml b/.air.toml index 9520ff3..91be4cb 100644 --- a/.air.toml +++ b/.air.toml @@ -1,11 +1,11 @@ -root = "./src" +root = "." tmp_dir = "tmp" [build] bin = "./tmp/main" cmd = "go build -o ./tmp/main ./src/." delay = 1000 - exclude_dir = ["assets", "tmp", "vendor"] + exclude_dir = ["tmp", "vendor", "ui/dist", "ui/node_modules", "ui/src"] exclude_file = [] exclude_regex = [] exclude_unchanged = false diff --git a/.golangci.yaml b/.golangci.yaml index 31a4beb..2e06378 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -18,6 +18,7 @@ linters: linters-settings: lll: line-length: 88 + tab-width: 4 gocognit: min-complexity: 10 nestif: diff --git a/go.mod b/go.mod index 2090ee2..8453017 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,21 @@ require ( github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 github.com/go-errors/errors v1.4.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect github.com/google/uuid v1.3.0 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.4.1 + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 7011440..8cd3adf 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEp code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -29,31 +30,42 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -67,8 +79,8 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -76,21 +88,32 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/fixtures/rauc_mock/main.go b/src/fixtures/rauc_mock/main.go index 58986ff..228aeda 100644 --- a/src/fixtures/rauc_mock/main.go +++ b/src/fixtures/rauc_mock/main.go @@ -48,27 +48,47 @@ const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` + `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` + `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}` -func printLinesWithDelay(lines string) { +func printLinesWithDelay(lines string, delay time.Duration) { for _, line := range strings.Split(lines, "\n") { fmt.Println(line) - time.Sleep(500 * time.Millisecond) + time.Sleep(delay) } } +func getBoolEnvvar(name string) bool { + val := strings.ToLower(os.Getenv(name)) + return val != "" && val != "false" && val != "0" +} + func main() { - arg := "" + method := "" if len(os.Args) > 1 { - arg = os.Args[1] + method = os.Args[1] } - switch arg { - case "fail": - printLinesWithDelay(outputFailure) + test := getBoolEnvvar("RAUC_MOCK_TEST") + failure := getBoolEnvvar("RAUC_MOCK_FAIL") + + delay := 500 * time.Millisecond + if test { + delay = 10 * time.Millisecond + } + + switch method { case "install": - printLinesWithDelay(outputSuccess) + if failure { + printLinesWithDelay(outputFailure, delay) + } else { + printLinesWithDelay(outputSuccess, delay) + } case "status": + if os.Args[2] != "--output-format=json" { + fmt.Println("output format must be json") + os.Exit(1) + } fmt.Println(statusJson) default: + fmt.Println("invalid method") os.Exit(1) } } diff --git a/src/main.go b/src/main.go index 0bbf0f0..8cbc312 100644 --- a/src/main.go +++ b/src/main.go @@ -7,14 +7,13 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/server" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" ) func main() { fmt.Println("SEBRAUC " + util.Version()) - mode.Set(util.Mode) if mode.IsDev() { fmt.Println("Test mode active - no update operations are executed.") diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go index 3a48b53..eb0d92d 100644 --- a/src/model/rauc_status.go +++ b/src/model/rauc_status.go @@ -5,6 +5,7 @@ package model // RaucStatus contains information about the current RAUC updater status. // //swagger:model RaucStatus +//nolint:lll type RaucStatus struct { // True if the installer is running // required: true diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index e84b142..691225c 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -26,12 +26,12 @@ type Rauc struct { func (r *Rauc) SetBroadcaster(bc util.Broadcaster) { r.bc = bc - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() } func (r *Rauc) completed(updateFile string) { r.status.Installing = false - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() _ = os.Remove(updateFile) } @@ -56,9 +56,10 @@ func (r *Rauc) RunRauc(updateFile string) error { r.status = model.RaucStatus{ Installing: true, } - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() - cmd := util.CommandFromString(fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) + cmd := util.CommandFromString( + fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) readPipe, _ := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout @@ -88,7 +89,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } if hasUpdate { - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() } } }() @@ -118,3 +119,7 @@ func (r *Rauc) GetStatusJson() []byte { statusJson, _ := json.Marshal(r.status) return statusJson } + +func (r *Rauc) bcStatus() { + r.bc.Broadcast(r.GetStatusJson()) +} diff --git a/src/rauc/rauc_test.go b/src/rauc/rauc_test.go new file mode 100644 index 0000000..df11109 --- /dev/null +++ b/src/rauc/rauc_test.go @@ -0,0 +1,116 @@ +package rauc + +import ( + "os" + "path/filepath" + "testing" + "time" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/stretchr/testify/assert" +) + +type broadcasterMock struct { + messages []string +} + +func (b *broadcasterMock) Broadcast(msg []byte) { + b.messages = append(b.messages, string(msg)) +} + +func TestRauc(t *testing.T) { + //nolint:lll + tests := []struct { + name string + fail string + messages []string + }{ + { + name: "ok", + fail: "", + messages: []string{ + "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Verifying signature\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Verifying signature done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking manifest contents\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n\"}", + "{\"installing\":true,\"percent\":60,\"message\":\"Checking manifest contents done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n\"}", + "{\"installing\":true,\"percent\":60,\"message\":\"Determining target install group\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Determining target install group done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Updating slots\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Checking slot rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n\"}", + "{\"installing\":true,\"percent\":90,\"message\":\"Checking slot rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n\"}", + "{\"installing\":true,\"percent\":90,\"message\":\"Copying image to rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Copying image to rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Updating slots done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\n\"}", + "{\"installing\":false,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\nInstalling `/app/tsgrain-update-raspberrypi3.raucb` succeeded\\n\"}", + }, + }, + { + name: "fail", + fail: "1", + messages: []string{ + "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\n\"}", + "{\"installing\":false,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\nInstalling /app/demo` failed\\n\"}", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os.Setenv("RAUC_MOCK_TEST", "1") + os.Setenv("RAUC_MOCK_FAIL", tt.fail) + + updater := &Rauc{} + bc := &broadcasterMock{} + updater.SetBroadcaster(bc) + + testfile := createTmpfile() + + err := updater.RunRauc(testfile) + assert.NoError(t, err) + + // Dont run multiple updates concurrently + err = updater.RunRauc(testfile) + assert.ErrorIs(t, err, util.ErrAlreadyRunning) + + // Wait for updater to finish + for updater.GetStatus().Installing { + time.Sleep(50 * time.Millisecond) + } + + assert.False(t, util.DoesFileExist(testfile), "update file was not deleted") + + assert.Equal(t, tt.messages, bc.messages) + }) + } +} + +func createTmpfile() string { + tmpdir, err := util.GetTmpdir() + if err != nil { + panic(err) + } + + tmpfile := filepath.Join(tmpdir, "test.raucb") + _, err = os.Create(tmpfile) + if err != nil { + panic(err) + } + + return tmpfile +} diff --git a/src/server/middleware/cache_test.go b/src/server/middleware/cache_test.go new file mode 100644 index 0000000..eaf7f84 --- /dev/null +++ b/src/server/middleware/cache_test.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCache(t *testing.T) { + router := gin.New() + router.Use(Cache) + router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "HelloWorld") }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "HelloWorld", w.Body.String()) + assert.Equal(t, "public, max-age=604800, immutable", + w.Header().Get("Cache-Control")) +} diff --git a/src/server/middleware/error_handler.go b/src/server/middleware/error_handler.go index e774c66..94aac0f 100644 --- a/src/server/middleware/error_handler.go +++ b/src/server/middleware/error_handler.go @@ -26,7 +26,7 @@ func ErrorHandler(isApi bool) gin.HandlerFunc { func PanicHandler(isApi bool) gin.HandlerFunc { return nice.Recovery(func(c *gin.Context, err interface{}) { - writeError(c, fmt.Errorf("panic: %s", err), isApi) + writeError(c, fmt.Errorf("[PANIC] %s", err), isApi) }) } diff --git a/src/server/middleware/error_handler_test.go b/src/server/middleware/error_handler_test.go new file mode 100644 index 0000000..4efedb1 --- /dev/null +++ b/src/server/middleware/error_handler_test.go @@ -0,0 +1,140 @@ +package middleware + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestErrorHandler(t *testing.T) { + tests := []struct { + name string + controller gin.HandlerFunc + isApi bool + expectResponse string + expectStatus int + }{ + { + name: "error", + controller: controllerError, + isApi: false, + expectResponse: "400 Bad Request: error test", + expectStatus: http.StatusBadRequest, + }, + { + name: "error_api", + controller: controllerError, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Bad Request","status_code":400,"msg":"error test"}`, + expectStatus: http.StatusBadRequest, + }, + { + name: "generic_error", + controller: controllerErrorGeneric, + isApi: false, + expectResponse: "500 Internal Server Error: generic error", + expectStatus: http.StatusInternalServerError, + }, + { + name: "generic_error_api", + controller: controllerErrorGeneric, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"generic error"}`, + expectStatus: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := gin.New() + router.Use(ErrorHandler(tt.isApi)) + router.GET("/", tt.controller) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, tt.expectStatus, w.Code) + assert.Equal(t, tt.expectResponse, w.Body.String()) + }) + } +} + +func TestPanicHandler(t *testing.T) { + tests := []struct { + name string + controller gin.HandlerFunc + isApi bool + expectResponse string + expectStatus int + }{ + { + name: "panic", + controller: controllerPanic, + isApi: false, + expectResponse: "500 Internal Server Error: [PANIC] panic message", + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_api", + controller: controllerPanic, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message"}`, + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_w_error", + controller: controllerPanicErr, + isApi: false, + expectResponse: "500 Internal Server Error: [PANIC] panic message in error", + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_w_error_api", + controller: controllerPanicErr, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message in error"}`, + expectStatus: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := gin.New() + router.Use(PanicHandler(tt.isApi)) + router.GET("/", tt.controller) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, tt.expectStatus, w.Code) + assert.Equal(t, tt.expectResponse, w.Body.String()) + }) + } +} + +func controllerError(c *gin.Context) { + c.Error(util.HttpErrNew("error test", http.StatusBadRequest)) +} + +func controllerErrorGeneric(c *gin.Context) { + c.Error(errors.New("generic error")) +} + +func controllerPanic(c *gin.Context) { + panic("panic message") +} + +func controllerPanicErr(c *gin.Context) { + panic(errors.New("panic message in error")) +} diff --git a/src/server/server.go b/src/server/server.go index 3f1eafb..74a2815 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -18,10 +18,10 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/model" "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "code.thetadev.de/TSGRain/SEBRAUC/ui" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -124,7 +124,7 @@ func (srv *SEBRAUCServer) Run() error { // $ref: "#/definitions/StatusMessage" // 409: // description: already running -// schema: +// schema: // $ref: "#/definitions/Error" // 500: // description: Server Error @@ -154,8 +154,6 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { err = srv.udater.RunRauc(updateFile) if err == nil { writeStatus(c, true, "Update started") - } else if errors.Is(err, util.ErrAlreadyRunning) { - writeStatus(c, false, "Updater already running") } else { c.Error(err) return diff --git a/src/server/stream/once_test.go b/src/server/stream/once_test.go index 53ec08d..720f65b 100644 --- a/src/server/stream/once_test.go +++ b/src/server/stream/once_test.go @@ -20,7 +20,7 @@ func Test_Execute(t *testing.T) { case <-execution: // expected case <-time.After(100 * time.Millisecond): - t.Fatal("fExecute should be executed once") + t.Fatal("Execute should be executed once") } select { diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go index a60c624..1e58ed6 100644 --- a/src/server/stream/stream.go +++ b/src/server/stream/stream.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) @@ -146,7 +146,8 @@ func newUpgrader(allowedWebSocketOrigins []string) *websocket.Upgrader { func compileAllowedWebSocketOrigins(allowedOrigins []string) []*regexp.Regexp { var compiledAllowedOrigins []*regexp.Regexp for _, origin := range allowedOrigins { - compiledAllowedOrigins = append(compiledAllowedOrigins, regexp.MustCompile(origin)) + compiledAllowedOrigins = append(compiledAllowedOrigins, + regexp.MustCompile(origin)) } return compiledAllowedOrigins diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go index 69f6dc2..ca24cca 100644 --- a/src/server/stream/stream_test.go +++ b/src/server/stream/stream_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "github.com/fortytw2/leaktest" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml index 3d85982..fb137ff 100644 --- a/src/server/swagger/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -216,6 +216,8 @@ paths: $ref: "#/definitions/StatusMessage" "409": description: already running + schema: + $ref: "#/definitions/Error" "500": description: Server Error schema: diff --git a/src/server/swagger/swagger_test.go b/src/server/swagger/swagger_test.go new file mode 100644 index 0000000..9fd3c03 --- /dev/null +++ b/src/server/swagger/swagger_test.go @@ -0,0 +1,44 @@ +package swagger + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestSwagger(t *testing.T) { + router := gin.New() + Register(router) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/swagger/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, swaggerHtml, w.Body.Bytes()) + assert.NotEmpty(t, w.Header().Get("Cache-Control")) + + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "/api/swagger/swagger.yaml", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, swaggerYaml, w.Body.Bytes()) + assert.NotEmpty(t, w.Header().Get("Cache-Control")) +} + +func TestSwaggerData(t *testing.T) { + assert.True(t, bytes.Contains(swaggerHtml, + []byte("https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js")), + "HTML data missing", + ) + + assert.True(t, bytes.Contains(swaggerYaml, + []byte("REST API for the SEBRAUC firmware updater")), + "YAML data missing", + ) +} diff --git a/src/util/commands.go b/src/util/commands.go index 78fb45d..3341b90 100644 --- a/src/util/commands.go +++ b/src/util/commands.go @@ -3,11 +3,11 @@ package util -import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" const ( RebootCmd = "shutdown -r 0" RaucCmd = "rauc" - Mode = mode.Prod + appmode = mode.Prod ) diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go index b447055..f52d17d 100644 --- a/src/util/commands_mock.go +++ b/src/util/commands_mock.go @@ -3,11 +3,11 @@ package util -import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" const ( RebootCmd = "touch /tmp/sebrauc_reboot_test" RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" - Mode = mode.Dev + appmode = mode.Dev ) diff --git a/src/util/errors.go b/src/util/errors.go index 1d3fc3b..62427f8 100644 --- a/src/util/errors.go +++ b/src/util/errors.go @@ -6,7 +6,7 @@ import ( ) var ( - ErrAlreadyRunning = errors.New("rauc already running") + ErrAlreadyRunning = HttpErrNew("rauc already running", http.StatusConflict) ErrFileDoesNotExist = errors.New("file does not exist") ErrPageNotFound = HttpErrNew("page not found", http.StatusNotFound) ) diff --git a/src/server/mode/mode.go b/src/util/mode/mode.go similarity index 89% rename from src/server/mode/mode.go rename to src/util/mode/mode.go index 59e297f..c8625f6 100644 --- a/src/server/mode/mode.go +++ b/src/util/mode/mode.go @@ -11,17 +11,16 @@ const ( TestDev = "testdev" ) -var mode = Dev +var currentMode = Dev -// Set sets the new mode. func Set(newMode string) { - mode = newMode + currentMode = newMode updateGinMode() } // Get returns the current mode. func Get() string { - return mode + return currentMode } // IsDev returns true if the current mode is dev mode. diff --git a/src/server/mode/mode_test.go b/src/util/mode/mode_test.go similarity index 100% rename from src/server/mode/mode_test.go rename to src/util/mode/mode_test.go diff --git a/src/util/version.go b/src/util/version.go index 7e8dba4..e9a1869 100644 --- a/src/util/version.go +++ b/src/util/version.go @@ -1,7 +1,13 @@ package util +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" + var version = "dev" +func init() { + mode.Set(appmode) +} + func Version() string { return version } diff --git a/ui/src/components/Updater/SysinfoCard.tsx b/ui/src/components/Updater/SysinfoCard.tsx index 04bd51c..625ed7c 100644 --- a/ui/src/components/Updater/SysinfoCard.tsx +++ b/ui/src/components/Updater/SysinfoCard.tsx @@ -23,6 +23,8 @@ type State = { } export default class SysinfoCard extends Component { + private fetchTimeout: number | undefined + constructor(props?: Props | undefined, context?: any) { super(props, context) this.fetchInfo() @@ -36,13 +38,12 @@ export default class SysinfoCard extends Component { this.setState({sysinfo: response.data}) } else { console.log("error fetching info", response.data) - console.log("error fetching info", response.data) - window.setTimeout(this.fetchInfo, 3000) + this.fetchTimeout = window.setTimeout(this.fetchInfo, 3000) } }) .catch((reason) => { console.log("error fetching info", reason) - window.setTimeout(this.fetchInfo, 3000) + this.fetchTimeout = window.setTimeout(this.fetchInfo, 3000) }) } @@ -150,6 +151,12 @@ export default class SysinfoCard extends Component { ) } + componentWillUnmount() { + if (this.fetchTimeout !== undefined) { + window.clearTimeout(this.fetchTimeout) + } + } + render() { if (this.state.sysinfo) { return this.renderSysinfo() diff --git a/ui/ui_test.go b/ui/ui_test.go new file mode 100644 index 0000000..acb0d46 --- /dev/null +++ b/ui/ui_test.go @@ -0,0 +1,23 @@ +package ui + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestUI(t *testing.T) { + router := gin.New() + Register(router) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "SEBRAUC") + assert.NotEmpty(t, w.Header().Get("Cache-Control")) +}