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
13 changes: 12 additions & 1 deletion internal/clab/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,20 +531,31 @@ func (s *Service) CreateCA(ctx context.Context, opts CACreateOptions) (*clabcert

ca := clabcert.NewCA()

// Go crypto/rsa rejects insecure key sizes; containerlab cert helpers
// accept 0 which used to imply a default. Enforce a safe default here.
keySize := opts.KeySize
if keySize == 0 {
keySize = 2048
}
if keySize < 2048 {
keySize = 2048
}

input := &clabcert.CACSRInput{
CommonName: opts.CommonName,
Country: opts.Country,
Locality: opts.Locality,
Organization: opts.Organization,
OrganizationUnit: opts.OrgUnit,
Expiry: opts.Expiry,
KeySize: opts.KeySize,
KeySize: keySize,
}

log.Info("Generating CA certificate",
"name", opts.Name,
"commonName", opts.CommonName,
"expiry", opts.Expiry,
"keySize", keySize,
)

cert, err := ca.GenerateCACert(input)
Expand Down
66 changes: 66 additions & 0 deletions tests_go/docs_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// tests_go/docs_suite_test.go
package tests_go

import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/suite"
)

// DocsSuite tests public documentation endpoints (swagger/redoc).
type DocsSuite struct {
BaseSuite
}

func TestDocsSuite(t *testing.T) {
suite.Run(t, new(DocsSuite))
}

func (s *DocsSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
}

func (s *DocsSuite) TestSwaggerUIIndexHTML() {
s.logTest("Fetching swagger UI index page")

url := fmt.Sprintf("%s/swagger/index.html", s.cfg.APIURL)
bodyBytes, statusCode, err := s.doRequest("GET", url, nil, nil, s.cfg.RequestTimeout)
s.Require().NoError(err)
s.Require().Equal(http.StatusOK, statusCode, "Expected 200 for swagger UI. Body: %s", string(bodyBytes))

s.Assert().NotEmpty(bodyBytes, "Expected swagger UI page content to be non-empty")
// Avoid strict string matching; just sanity-check it's HTML-ish.
s.Assert().True(strings.Contains(strings.ToLower(string(bodyBytes)), "<html"), "Expected HTML response for swagger UI")
}

func (s *DocsSuite) TestSwaggerDocJSON() {
s.logTest("Fetching swagger doc.json")

url := fmt.Sprintf("%s/swagger/doc.json", s.cfg.APIURL)
bodyBytes, statusCode, err := s.doRequest("GET", url, nil, nil, s.cfg.RequestTimeout)
s.Require().NoError(err)
s.Require().Equal(http.StatusOK, statusCode, "Expected 200 for swagger doc.json. Body: %s", string(bodyBytes))

var doc map[string]interface{}
s.Require().NoError(json.Unmarshal(bodyBytes, &doc), "Expected JSON swagger document. Body: %s", string(bodyBytes))
// swaggo typically emits Swagger 2.0 with top-level "swagger": "2.0".
_, hasSwagger := doc["swagger"]
_, hasOpenAPI := doc["openapi"]
s.Assert().True(hasSwagger || hasOpenAPI, "Expected swagger/openapi field in doc.json")
}

func (s *DocsSuite) TestRedocPage() {
s.logTest("Fetching redoc page")

url := fmt.Sprintf("%s/redoc", s.cfg.APIURL)
bodyBytes, statusCode, err := s.doRequest("GET", url, nil, nil, s.cfg.RequestTimeout)
s.Require().NoError(err)
s.Require().Equal(http.StatusOK, statusCode, "Expected 200 for redoc. Body: %s", string(bodyBytes))

s.Assert().NotEmpty(bodyBytes, "Expected redoc page content to be non-empty")
s.Assert().Contains(string(bodyBytes), `<redoc spec-url="/swagger/doc.json">`, "Expected redoc HTML to reference swagger spec URL")
}
104 changes: 104 additions & 0 deletions tests_go/lab_archive_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// tests_go/lab_archive_suite_test.go
package tests_go

import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/suite"
"gopkg.in/yaml.v3"
)

// LabArchiveSuite tests deploying labs from an uploaded archive.
type LabArchiveSuite struct {
BaseSuite
apiUserToken string
apiUserHeaders http.Header
}

func TestLabArchiveSuite(t *testing.T) {
suite.Run(t, new(LabArchiveSuite))
}

func (s *LabArchiveSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.apiUserToken = s.login(s.cfg.APIUserUser, s.cfg.APIUserPass)
s.apiUserHeaders = s.getAuthHeaders(s.apiUserToken)
s.Require().NotEmpty(s.apiUserToken)
}

func (s *LabArchiveSuite) TestDeployLabArchiveMissingLabNameQueryParam() {
s.logTest("Deploying lab archive without labName query param (expecting 400)")

var body bytes.Buffer
writer := multipart.NewWriter(&body)
s.Require().NoError(writer.Close())

headers := s.getAuthHeaders(s.apiUserToken)
headers.Set("Content-Type", writer.FormDataContentType())

url := fmt.Sprintf("%s/api/v1/labs/archive", s.cfg.APIURL)
respBody, statusCode, err := s.doRequest("POST", url, headers, &body, s.cfg.RequestTimeout)
s.Require().NoError(err)
s.Assert().Equal(http.StatusBadRequest, statusCode, "Expected 400 for missing labName. Body: %s", string(respBody))
}

func (s *LabArchiveSuite) TestDeployLabArchiveZipSuccess() {
labName := fmt.Sprintf("%s-arch-%s", s.cfg.LabNamePrefix, s.randomSuffix(5))
defer s.cleanupLab(labName, true)

s.logTest("Deploying lab '%s' via /api/v1/labs/archive (zip upload)", labName)

// Build YAML topology from the same JSON content used by other tests.
topologyJSON := strings.ReplaceAll(s.cfg.SimpleTopologyContent, "{lab_name}", labName)
var topoData map[string]interface{}
s.Require().NoError(json.Unmarshal([]byte(topologyJSON), &topoData), "Failed to parse test topology JSON")

yamlBytes, err := yaml.Marshal(topoData)
s.Require().NoError(err, "Failed to marshal topology to YAML")

// Create a zip archive in memory containing "<labName>.clab.yml" at the archive root.
var zipBuf bytes.Buffer
zw := zip.NewWriter(&zipBuf)
fw, err := zw.Create(labName + ".clab.yml")
s.Require().NoError(err, "Failed to create zip entry")
_, err = fw.Write(yamlBytes)
s.Require().NoError(err, "Failed to write zip entry data")
s.Require().NoError(zw.Close(), "Failed to close zip writer")

// Create multipart/form-data request body.
var body bytes.Buffer
writer := multipart.NewWriter(&body)

part, err := writer.CreateFormFile("labArchive", labName+".zip")
s.Require().NoError(err, "Failed to create multipart file part")
_, err = part.Write(zipBuf.Bytes())
s.Require().NoError(err, "Failed to write multipart file bytes")

s.Require().NoError(writer.Close(), "Failed to close multipart writer")

headers := s.getAuthHeaders(s.apiUserToken)
headers.Set("Content-Type", writer.FormDataContentType())

url := fmt.Sprintf("%s/api/v1/labs/archive?labName=%s", s.cfg.APIURL, labName)
respBody, statusCode, err := s.doRequest("POST", url, headers, &body, s.cfg.DeployTimeout)
s.Require().NoError(err)
s.Require().Equal(http.StatusOK, statusCode, "Expected 200 deploying lab from archive. Body: %s", string(respBody))

var out ClabInspectOutput
s.Require().NoError(json.Unmarshal(respBody, &out), "Failed to unmarshal archive deploy response. Body: %s", string(respBody))
s.Assert().Contains(out, labName, "Expected deployed lab '%s' to be present in response", labName)
if nodes, ok := out[labName]; ok {
s.Assert().NotEmpty(nodes, "Expected deployed lab to have container entries in response")
}

if !s.T().Failed() {
s.logSuccess("Archive deploy succeeded for lab '%s'", labName)
}
}
Loading