@@ -17,6 +17,7 @@ limitations under the License.
1717package picod
1818
1919import (
20+ "bytes"
2021 "crypto/rand"
2122 "crypto/rsa"
2223 "crypto/x509"
@@ -31,6 +32,7 @@ import (
3132 "time"
3233
3334 "github.com/gin-gonic/gin"
35+ "github.com/golang-jwt/jwt/v5"
3436 "github.com/stretchr/testify/assert"
3537 "github.com/stretchr/testify/require"
3638)
@@ -368,3 +370,117 @@ func TestNewServer_DifferentPorts(t *testing.T) {
368370 })
369371 }
370372}
373+
374+ func TestInitHandler (t * testing.T ) {
375+ tmpDir , err := os .MkdirTemp ("" , "picod-server-test-*" )
376+ require .NoError (t , err )
377+ defer os .RemoveAll (tmpDir )
378+
379+ // Generate bootstrap keys
380+ bootstrapPrivKey , err := rsa .GenerateKey (rand .Reader , 2048 )
381+ require .NoError (t , err )
382+
383+ pubKeyBytes , err := x509 .MarshalPKIXPublicKey (& bootstrapPrivKey .PublicKey )
384+ require .NoError (t , err )
385+
386+ pubKeyPEM := pem .EncodeToMemory (& pem.Block {
387+ Type : "PUBLIC KEY" ,
388+ Bytes : pubKeyBytes ,
389+ })
390+
391+ os .Setenv (BootstrapPublicKeyEnvVar , string (pubKeyPEM ))
392+ defer os .Unsetenv (BootstrapPublicKeyEnvVar )
393+
394+ config := Config {
395+ Port : 8080 ,
396+ Workspace : tmpDir ,
397+ }
398+ server := NewServer (config )
399+
400+ // Helper to generate a token signed by bootstrap private key
401+ generateToken := func (claims jwt.MapClaims ) string {
402+ token := jwt .NewWithClaims (jwt .SigningMethodRS256 , claims )
403+ tokenStr , err := token .SignedString (bootstrapPrivKey )
404+ require .NoError (t , err )
405+ return tokenStr
406+ }
407+
408+ sessionPubPEM := generateTestPublicKeyPEM (t )
409+
410+ t .Run ("invalid request format" , func (t * testing.T ) {
411+ w := httptest .NewRecorder ()
412+ c , _ := gin .CreateTestContext (w )
413+ c .Request , _ = http .NewRequest ("POST" , "/init" , bytes .NewBufferString ("{invalid-json}" ))
414+ c .Request .Header .Set ("Content-Type" , "application/json" )
415+
416+ server .InitHandler (c )
417+
418+ assert .Equal (t , http .StatusBadRequest , w .Code )
419+ var resp map [string ]string
420+ err := json .Unmarshal (w .Body .Bytes (), & resp )
421+ require .NoError (t , err )
422+ assert .Contains (t , resp ["error" ], "invalid request format" )
423+ })
424+
425+ t .Run ("invalid token" , func (t * testing.T ) {
426+ w := httptest .NewRecorder ()
427+ c , _ := gin .CreateTestContext (w )
428+ body , _ := json .Marshal (map [string ]string {"token" : "invalid.token.here" })
429+ c .Request , _ = http .NewRequest ("POST" , "/init" , bytes .NewBuffer (body ))
430+ c .Request .Header .Set ("Content-Type" , "application/json" )
431+
432+ server .InitHandler (c )
433+
434+ assert .Equal (t , http .StatusUnauthorized , w .Code )
435+ var resp map [string ]string
436+ err := json .Unmarshal (w .Body .Bytes (), & resp )
437+ require .NoError (t , err )
438+ assert .Contains (t , resp ["error" ], "invalid bootstrap token" )
439+ })
440+
441+ t .Run ("successful initialization" , func (t * testing.T ) {
442+ claims := jwt.MapClaims {
443+ "exp" : time .Now ().Add (time .Minute ).Unix (),
444+ "iat" : time .Now ().Unix (),
445+ "session_public_key" : sessionPubPEM ,
446+ }
447+ token := generateToken (claims )
448+
449+ w := httptest .NewRecorder ()
450+ c , _ := gin .CreateTestContext (w )
451+ body , _ := json .Marshal (map [string ]string {"token" : token })
452+ c .Request , _ = http .NewRequest ("POST" , "/init" , bytes .NewBuffer (body ))
453+ c .Request .Header .Set ("Content-Type" , "application/json" )
454+
455+ server .InitHandler (c )
456+
457+ assert .Equal (t , http .StatusOK , w .Code )
458+ var resp map [string ]string
459+ err := json .Unmarshal (w .Body .Bytes (), & resp )
460+ require .NoError (t , err )
461+ assert .Equal (t , "initialized successfully" , resp ["status" ])
462+ })
463+
464+ t .Run ("already initialized" , func (t * testing.T ) {
465+ claims := jwt.MapClaims {
466+ "exp" : time .Now ().Add (time .Minute ).Unix (),
467+ "iat" : time .Now ().Unix (),
468+ "session_public_key" : sessionPubPEM ,
469+ }
470+ token := generateToken (claims )
471+
472+ w := httptest .NewRecorder ()
473+ c , _ := gin .CreateTestContext (w )
474+ body , _ := json .Marshal (map [string ]string {"token" : token })
475+ c .Request , _ = http .NewRequest ("POST" , "/init" , bytes .NewBuffer (body ))
476+ c .Request .Header .Set ("Content-Type" , "application/json" )
477+
478+ server .InitHandler (c )
479+
480+ assert .Equal (t , http .StatusConflict , w .Code )
481+ var resp map [string ]string
482+ err := json .Unmarshal (w .Body .Bytes (), & resp )
483+ require .NoError (t , err )
484+ assert .Equal (t , "session already initialized" , resp ["error" ])
485+ })
486+ }
0 commit comments