test(ws): Ensure test files exist in backend for any executed code #381

Signed-off-by: Hen Schwartz (EXT-Nokia) <hen.schwartz.ext@nokia.com>
This commit is contained in:
Hen Schwartz (EXT-Nokia) 2025-06-09 14:54:14 +03:00
parent b2bee1d04b
commit 7d23ad1682
6 changed files with 963 additions and 9 deletions

View File

@ -31,3 +31,10 @@ const (
ServiceStatusHealthy ServiceStatus = "Healthy"
ServiceStatusUnhealthy ServiceStatus = "Unhealthy"
)
func NewHealthCheck(status ServiceStatus, version string) HealthCheck {
return HealthCheck{
Status: status,
SystemInfo: SystemInfo{Version: version},
}
}

View File

@ -0,0 +1,288 @@
/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package workspacekinds_test
import (
"testing"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
workspacekinds "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspacekinds"
)
func TestWorkspaceKinds(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "WorkspaceKinds Suite")
}
type workspaceKindTestCase struct {
name string
workspaceKind *kubefloworgv1beta1.WorkspaceKind
expectedName string
expectedDisplayName string
expectedDescription string
expectedDeprecated bool
expectedDeprecationMsg string
expectedHidden bool
expectedIconURL string
expectedLogoURL string
expectedPodLabels map[string]string
expectedPodAnnotations map[string]string
additionalValidations func(result workspacekinds.WorkspaceKind)
}
var _ = Describe("WorkspaceKind Types", func() {
Describe("NewWorkspaceKindModelFromWorkspaceKind", func() {
DescribeTable("should create WorkspaceKind models correctly",
func(tc workspaceKindTestCase) {
result := workspacekinds.NewWorkspaceKindModelFromWorkspaceKind(tc.workspaceKind)
// Basic assertions that apply to all test cases
Expect(result).ToNot(BeNil())
Expect(result.Name).To(Equal(tc.expectedName))
Expect(result.DisplayName).To(Equal(tc.expectedDisplayName))
Expect(result.Description).To(Equal(tc.expectedDescription))
Expect(result.Deprecated).To(Equal(tc.expectedDeprecated))
Expect(result.DeprecationMessage).To(Equal(tc.expectedDeprecationMsg))
Expect(result.Hidden).To(Equal(tc.expectedHidden))
Expect(result.Icon.URL).To(Equal(tc.expectedIconURL))
Expect(result.Logo.URL).To(Equal(tc.expectedLogoURL))
// Pod metadata assertions
if tc.expectedPodLabels != nil {
for key, value := range tc.expectedPodLabels {
Expect(result.PodTemplate.PodMetadata.Labels).To(HaveKeyWithValue(key, value))
}
}
if tc.expectedPodAnnotations != nil {
for key, value := range tc.expectedPodAnnotations {
Expect(result.PodTemplate.PodMetadata.Annotations).To(HaveKeyWithValue(key, value))
}
}
// Run any additional custom validations
if tc.additionalValidations != nil {
tc.additionalValidations(result)
}
},
Entry("complete WorkspaceKind with all fields", workspaceKindTestCase{
name: "complete WorkspaceKind",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "test-workspacekind",
Namespace: "kubeflow",
Labels: map[string]string{
"app": "workspacekind",
"version": "v1",
},
Annotations: map[string]string{
"description": "Complete workspacekind",
},
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Complete Workspacekind",
Description: "A complete test workspacekind with all features",
Deprecated: ptr.To(false),
Hidden: ptr.To(false),
Icon: kubefloworgv1beta1.WorkspaceKindIcon{
Url: ptr.To("/workspaces/backend/api/v1/workspacekinds/test-workspacekind/assets/icon"),
},
Logo: kubefloworgv1beta1.WorkspaceKindIcon{
Url: ptr.To("/workspaces/backend/api/v1/workspacekinds/test-workspacekind/assets/logo"),
},
},
PodTemplate: kubefloworgv1beta1.WorkspaceKindPodTemplate{
PodMetadata: &kubefloworgv1beta1.WorkspaceKindPodMetadata{
Labels: map[string]string{
"app": "test-workspacekind",
},
Annotations: map[string]string{
"annotation-key": "annotation-value",
},
},
},
},
},
expectedName: "test-workspacekind",
expectedDisplayName: "Complete Workspacekind",
expectedDescription: "A complete test workspacekind with all features",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/test-workspacekind/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/test-workspacekind/assets/logo",
expectedPodLabels: map[string]string{"app": "test-workspacekind"},
expectedPodAnnotations: map[string]string{"annotation-key": "annotation-value"},
}),
Entry("empty strings in required fields", workspaceKindTestCase{
name: "empty strings in required fields",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "empty-fields-workspace",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "",
Description: "",
},
},
},
expectedName: "empty-fields-workspace",
expectedDisplayName: "",
expectedDescription: "",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/empty-fields-workspace/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/empty-fields-workspace/assets/logo",
}),
Entry("special characters in names", workspaceKindTestCase{
name: "special characters in names",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "special-chars_workspace-123",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Special-Chars_Workspace 123!",
Description: "Workspace with special characters in name",
},
},
},
expectedName: "special-chars_workspace-123",
expectedDisplayName: "Special-Chars_Workspace 123!",
expectedDescription: "Workspace with special characters in name",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/special-chars_workspace-123/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/special-chars_workspace-123/assets/logo",
}),
Entry("long descriptions", workspaceKindTestCase{
name: "long descriptions",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "long-description-workspace",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Long Description Workspace",
Description: "This is a very long description that contains multiple sentences. " +
"It describes a workspace kind that has many features and capabilities. " +
"The description should be preserved exactly as provided without truncation. " +
"This tests the ability to handle larger text fields in the workspace kind model.",
},
},
},
expectedName: "long-description-workspace",
expectedDisplayName: "Long Description Workspace",
expectedDescription: "This is a very long description that contains multiple sentences. " +
"It describes a workspace kind that has many features and capabilities. " +
"The description should be preserved exactly as provided without truncation. " +
"This tests the ability to handle larger text fields in the workspace kind model.",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/long-description-workspace/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/long-description-workspace/assets/logo",
additionalValidations: func(result workspacekinds.WorkspaceKind) {
Expect(len(result.Description)).To(BeNumerically(">", 200))
},
}),
Entry("deprecated WorkspaceKind", workspaceKindTestCase{
name: "deprecated WorkspaceKind",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "deprecated-workspace",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Deprecated Workspace",
Description: "This workspace kind is deprecated",
Deprecated: ptr.To(true),
DeprecationMessage: ptr.To("Use new-workspace instead"),
},
},
},
expectedName: "deprecated-workspace",
expectedDisplayName: "Deprecated Workspace",
expectedDescription: "This workspace kind is deprecated",
expectedDeprecated: true,
expectedDeprecationMsg: "Use new-workspace instead",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/deprecated-workspace/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/deprecated-workspace/assets/logo",
}),
Entry("hidden WorkspaceKind", workspaceKindTestCase{
name: "hidden WorkspaceKind",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "hidden-workspace",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Hidden Workspace",
Description: "This workspace kind is hidden from UI",
Hidden: ptr.To(true),
},
},
},
expectedName: "hidden-workspace",
expectedDisplayName: "Hidden Workspace",
expectedDescription: "This workspace kind is hidden from UI",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: true,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/hidden-workspace/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/hidden-workspace/assets/logo",
}),
Entry("minimal WorkspaceKind with only required fields", workspaceKindTestCase{
name: "minimal WorkspaceKind",
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: "minimal-workspace",
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Minimal Workspace",
},
},
},
expectedName: "minimal-workspace",
expectedDisplayName: "Minimal Workspace",
expectedDescription: "",
expectedDeprecated: false,
expectedDeprecationMsg: "",
expectedHidden: false,
expectedIconURL: "/workspaces/backend/api/v1/workspacekinds/minimal-workspace/assets/icon",
expectedLogoURL: "/workspaces/backend/api/v1/workspacekinds/minimal-workspace/assets/logo",
}),
)
})
})

View File

@ -0,0 +1,574 @@
/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package workspaces_test
import (
"time"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
workspaces "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces"
)
// Test constants
const (
testWorkspaceName = "test-workspace"
testNamespace = "test-namespace"
testWorkspaceKindName = "jupyter-notebook"
testUID = "test-uid"
testHomePVC = "home-pvc"
testHomeMountPath = "/home/jovyan"
testDataPVC1 = "data-pvc-1"
testDataPVC2 = "data-pvc-2"
testDataMountPath1 = "/data1"
testDataMountPath2 = "/data2"
testImageConfigID = "default-image"
testPodConfigID = "default-pod"
testDesiredImageID = "desired-image"
testDesiredPodID = "desired-pod"
testOldImageID = "old-image"
testOldPodID = "old-pod"
testNewImageID = "new-image"
testNewPodID = "new-pod"
testStateMessage = "Running successfully"
testAppLabel = "test-app"
testAnnotationKey = "annotation-key"
testAnnotationValue = "annotation-value"
testEnvironmentLabel = "environment"
testPythonValue = "python"
testSizeLabel = "size"
testSmallValue = "small"
testJupyterPortID = "jupyter"
testJupyterDisplayName = "Jupyter"
)
// Test data structures
type WorkspaceBuilder struct {
workspace *kubefloworgv1beta1.Workspace
}
type WorkspaceKindBuilder struct {
workspaceKind *kubefloworgv1beta1.WorkspaceKind
}
type TestExpectations struct {
Name string
Namespace string
WorkspaceKindName string
WorkspaceKindMissing bool
State workspaces.WorkspaceState
StateMessage string
DeferUpdates bool
Paused bool
PendingRestart bool
HomePVCName string
HomeMountPath string
HomeReadOnly bool
DataVolumeCount int
ImageConfigID string
PodConfigID string
ServiceCount int
Labels map[string]string
Annotations map[string]string
}
// Workspace builder functions
func NewWorkspaceBuilder() *WorkspaceBuilder {
return &WorkspaceBuilder{
workspace: &kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Name: testWorkspaceName,
Namespace: testNamespace,
},
Spec: kubefloworgv1beta1.WorkspaceSpec{
Kind: testWorkspaceKindName,
DeferUpdates: ptr.To(false),
Paused: ptr.To(false),
},
Status: kubefloworgv1beta1.WorkspaceStatus{
State: kubefloworgv1beta1.WorkspaceStateRunning,
StateMessage: testStateMessage,
PendingRestart: false,
},
},
}
}
func (wb *WorkspaceBuilder) WithName(name string) *WorkspaceBuilder {
wb.workspace.Name = name
return wb
}
func (wb *WorkspaceBuilder) WithNamespace(namespace string) *WorkspaceBuilder {
wb.workspace.Namespace = namespace
return wb
}
func (wb *WorkspaceBuilder) WithKind(kind string) *WorkspaceBuilder {
wb.workspace.Spec.Kind = kind
return wb
}
func (wb *WorkspaceBuilder) WithState(state kubefloworgv1beta1.WorkspaceState) *WorkspaceBuilder {
wb.workspace.Status.State = state
return wb
}
func (wb *WorkspaceBuilder) WithStateMessage(message string) *WorkspaceBuilder {
wb.workspace.Status.StateMessage = message
return wb
}
func (wb *WorkspaceBuilder) WithPodMetadata(labels, annotations map[string]string) *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.PodMetadata = &kubefloworgv1beta1.WorkspacePodMetadata{
Labels: labels,
Annotations: annotations,
}
return wb
}
func (wb *WorkspaceBuilder) WithHomeVolume(pvcName string) *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.Volumes.Home = ptr.To(pvcName)
return wb
}
func (wb *WorkspaceBuilder) WithoutHomeVolume() *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.Volumes.Home = nil
return wb
}
func (wb *WorkspaceBuilder) WithDataVolumes(volumes []kubefloworgv1beta1.PodVolumeMount) *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.Volumes.Data = volumes
return wb
}
func (wb *WorkspaceBuilder) WithImageConfig(imageConfig string) *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.Options.ImageConfig = imageConfig
return wb
}
func (wb *WorkspaceBuilder) WithPodConfig(podConfig string) *WorkspaceBuilder {
wb.workspace.Spec.PodTemplate.Options.PodConfig = podConfig
return wb
}
func (wb *WorkspaceBuilder) WithDesiredImageConfig(desired string) *WorkspaceBuilder {
wb.workspace.Status.PodTemplateOptions.ImageConfig.Desired = desired
return wb
}
func (wb *WorkspaceBuilder) WithDesiredPodConfig(desired string) *WorkspaceBuilder {
wb.workspace.Status.PodTemplateOptions.PodConfig.Desired = desired
return wb
}
func (wb *WorkspaceBuilder) WithImageRedirectChain(chain []kubefloworgv1beta1.WorkspacePodOptionRedirectStep) *WorkspaceBuilder {
wb.workspace.Status.PodTemplateOptions.ImageConfig.RedirectChain = chain
return wb
}
func (wb *WorkspaceBuilder) WithPodRedirectChain(chain []kubefloworgv1beta1.WorkspacePodOptionRedirectStep) *WorkspaceBuilder {
wb.workspace.Status.PodTemplateOptions.PodConfig.RedirectChain = chain
return wb
}
func (wb *WorkspaceBuilder) WithActivity(lastActivity, lastUpdate int64) *WorkspaceBuilder {
wb.workspace.Status.Activity = kubefloworgv1beta1.WorkspaceActivity{
LastActivity: lastActivity,
LastUpdate: lastUpdate,
}
return wb
}
func (wb *WorkspaceBuilder) Build() *kubefloworgv1beta1.Workspace {
return wb.workspace
}
// WorkspaceKind builder functions
func NewWorkspaceKindBuilder() *WorkspaceKindBuilder {
return &WorkspaceKindBuilder{
workspaceKind: &kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{
Name: testWorkspaceKindName,
UID: types.UID(testUID),
},
Spec: kubefloworgv1beta1.WorkspaceKindSpec{
Spawner: kubefloworgv1beta1.WorkspaceKindSpawner{
DisplayName: "Jupyter Notebook",
Description: "A Jupyter notebook workspace",
},
PodTemplate: kubefloworgv1beta1.WorkspaceKindPodTemplate{
VolumeMounts: kubefloworgv1beta1.WorkspaceKindVolumeMounts{
Home: testHomeMountPath,
},
},
},
},
}
}
func (wkb *WorkspaceKindBuilder) WithName(name string) *WorkspaceKindBuilder {
wkb.workspaceKind.Name = name
return wkb
}
func (wkb *WorkspaceKindBuilder) WithUID(uid string) *WorkspaceKindBuilder {
wkb.workspaceKind.UID = types.UID(uid)
return wkb
}
func (wkb *WorkspaceKindBuilder) WithoutUID() *WorkspaceKindBuilder {
wkb.workspaceKind.UID = ""
return wkb
}
func (wkb *WorkspaceKindBuilder) WithHomeMountPath(path string) *WorkspaceKindBuilder {
wkb.workspaceKind.Spec.PodTemplate.VolumeMounts.Home = path
return wkb
}
func (wkb *WorkspaceKindBuilder) WithImageConfigs(configs []kubefloworgv1beta1.ImageConfigValue) *WorkspaceKindBuilder {
wkb.workspaceKind.Spec.PodTemplate.Options.ImageConfig.Values = configs
return wkb
}
func (wkb *WorkspaceKindBuilder) WithPodConfigs(configs []kubefloworgv1beta1.PodConfigValue) *WorkspaceKindBuilder {
wkb.workspaceKind.Spec.PodTemplate.Options.PodConfig.Values = configs
return wkb
}
func (wkb *WorkspaceKindBuilder) Build() *kubefloworgv1beta1.WorkspaceKind {
return wkb.workspaceKind
}
// Helper functions for creating common test data
func createDefaultDataVolumes() []kubefloworgv1beta1.PodVolumeMount {
return []kubefloworgv1beta1.PodVolumeMount{
{
PVCName: testDataPVC1,
MountPath: testDataMountPath1,
ReadOnly: ptr.To(false),
},
{
PVCName: testDataPVC2,
MountPath: testDataMountPath2,
ReadOnly: ptr.To(true),
},
}
}
func createDefaultPodMetadata() (map[string]string, map[string]string) {
labels := map[string]string{testAppLabel: testAppLabel}
annotations := map[string]string{testAnnotationKey: testAnnotationValue}
return labels, annotations
}
func createDefaultImageConfigs() []kubefloworgv1beta1.ImageConfigValue {
return []kubefloworgv1beta1.ImageConfigValue{
{
Id: testImageConfigID,
Spawner: kubefloworgv1beta1.OptionSpawnerInfo{
DisplayName: "Default Image",
Description: ptr.To("Default Jupyter image"),
Labels: []kubefloworgv1beta1.OptionSpawnerLabel{
{Key: testEnvironmentLabel, Value: testPythonValue},
},
},
Spec: kubefloworgv1beta1.ImageConfigSpec{
Ports: []kubefloworgv1beta1.ImagePort{
{
Id: testJupyterPortID,
DisplayName: testJupyterDisplayName,
Protocol: kubefloworgv1beta1.ImagePortProtocolHTTP,
},
},
},
},
{
Id: testOldImageID,
Spawner: kubefloworgv1beta1.OptionSpawnerInfo{
DisplayName: "Old Image",
Description: ptr.To("Deprecated image"),
},
Redirect: &kubefloworgv1beta1.OptionRedirect{
To: testNewImageID,
Message: &kubefloworgv1beta1.RedirectMessage{
Text: "This image has been deprecated",
Level: kubefloworgv1beta1.RedirectMessageLevelWarning,
},
},
},
}
}
func createDefaultPodConfigs() []kubefloworgv1beta1.PodConfigValue {
return []kubefloworgv1beta1.PodConfigValue{
{
Id: testPodConfigID,
Spawner: kubefloworgv1beta1.OptionSpawnerInfo{
DisplayName: "Default Pod Config",
Description: ptr.To("Default pod configuration"),
Labels: []kubefloworgv1beta1.OptionSpawnerLabel{
{Key: testSizeLabel, Value: testSmallValue},
},
},
},
{
Id: testOldPodID,
Spawner: kubefloworgv1beta1.OptionSpawnerInfo{
DisplayName: "Old Pod Config",
Description: ptr.To("Deprecated pod config"),
},
Redirect: &kubefloworgv1beta1.OptionRedirect{
To: testNewPodID,
Message: &kubefloworgv1beta1.RedirectMessage{
Text: "This pod config has been deprecated",
Level: kubefloworgv1beta1.RedirectMessageLevelDanger,
},
},
},
}
}
func createDefaultRedirectChain() []kubefloworgv1beta1.WorkspacePodOptionRedirectStep {
return []kubefloworgv1beta1.WorkspacePodOptionRedirectStep{
{Source: testOldImageID, Target: testNewImageID},
}
}
// Assertion helper functions
func expectBasicWorkspaceFields(result *workspaces.Workspace, expectations *TestExpectations) {
Expect(result.Name).To(Equal(expectations.Name))
Expect(result.Namespace).To(Equal(expectations.Namespace))
Expect(result.WorkspaceKind.Name).To(Equal(expectations.WorkspaceKindName))
Expect(result.WorkspaceKind.Missing).To(Equal(expectations.WorkspaceKindMissing))
Expect(result.State).To(Equal(expectations.State))
Expect(result.StateMessage).To(Equal(expectations.StateMessage))
Expect(result.DeferUpdates).To(Equal(expectations.DeferUpdates))
Expect(result.Paused).To(Equal(expectations.Paused))
Expect(result.PendingRestart).To(Equal(expectations.PendingRestart))
}
func expectHomeVolume(result *workspaces.Workspace, expectations *TestExpectations) {
if expectations.HomePVCName == "" {
Expect(result.PodTemplate.Volumes.Home).To(BeNil())
} else {
Expect(result.PodTemplate.Volumes.Home).ToNot(BeNil())
Expect(result.PodTemplate.Volumes.Home.PVCName).To(Equal(expectations.HomePVCName))
Expect(result.PodTemplate.Volumes.Home.MountPath).To(Equal(expectations.HomeMountPath))
Expect(result.PodTemplate.Volumes.Home.ReadOnly).To(Equal(expectations.HomeReadOnly))
}
}
func expectDataVolumes(result *workspaces.Workspace, expectations *TestExpectations) {
Expect(result.PodTemplate.Volumes.Data).To(HaveLen(expectations.DataVolumeCount))
}
func expectPodMetadata(result *workspaces.Workspace, expectations *TestExpectations) {
if expectations.Labels != nil {
for key, value := range expectations.Labels {
Expect(result.PodTemplate.PodMetadata.Labels).To(HaveKeyWithValue(key, value))
}
}
if expectations.Annotations != nil {
for key, value := range expectations.Annotations {
Expect(result.PodTemplate.PodMetadata.Annotations).To(HaveKeyWithValue(key, value))
}
}
}
func expectServices(result *workspaces.Workspace, expectations *TestExpectations) {
if expectations.ServiceCount == 0 {
Expect(result.Services).To(BeNil())
} else {
Expect(result.Services).To(HaveLen(expectations.ServiceCount))
}
}
func validateWorkspaceModel(result *workspaces.Workspace, expectations *TestExpectations) {
expectBasicWorkspaceFields(result, expectations)
expectHomeVolume(result, expectations)
expectDataVolumes(result, expectations)
expectPodMetadata(result, expectations)
expectServices(result, expectations)
}
var _ = Describe("Workspace Functions", func() {
var testTime = metav1.NewTime(time.Now())
Describe("NewWorkspaceModelFromWorkspace", func() {
DescribeTable("should create workspace models correctly",
func(
workspaceBuilder func() *kubefloworgv1beta1.Workspace,
workspaceKindBuilder func() *kubefloworgv1beta1.WorkspaceKind,
expectations TestExpectations,
) {
workspace := workspaceBuilder()
workspaceKind := workspaceKindBuilder()
result := workspaces.NewWorkspaceModelFromWorkspace(workspace, workspaceKind)
validateWorkspaceModel(&result, &expectations)
},
Entry("complete workspace with matching workspace kind",
func() *kubefloworgv1beta1.Workspace {
labels, annotations := createDefaultPodMetadata()
return NewWorkspaceBuilder().
WithPodMetadata(labels, annotations).
WithHomeVolume(testHomePVC).
WithDataVolumes(createDefaultDataVolumes()).
WithImageConfig(testImageConfigID).
WithPodConfig(testPodConfigID).
WithDesiredImageConfig(testDesiredImageID).
WithDesiredPodConfig(testDesiredPodID).
WithImageRedirectChain(createDefaultRedirectChain()).
WithPodRedirectChain(createDefaultRedirectChain()).
WithActivity(testTime.Unix(), testTime.Unix()).
Build()
},
func() *kubefloworgv1beta1.WorkspaceKind {
return NewWorkspaceKindBuilder().
WithImageConfigs(createDefaultImageConfigs()).
WithPodConfigs(createDefaultPodConfigs()).
Build()
},
TestExpectations{
Name: testWorkspaceName,
Namespace: testNamespace,
WorkspaceKindName: testWorkspaceKindName,
WorkspaceKindMissing: false,
State: workspaces.WorkspaceStateRunning,
StateMessage: testStateMessage,
DeferUpdates: false,
Paused: false,
PendingRestart: false,
HomePVCName: testHomePVC,
HomeMountPath: testHomeMountPath,
HomeReadOnly: false,
DataVolumeCount: 2,
ServiceCount: 1,
Labels: map[string]string{testAppLabel: testAppLabel},
Annotations: map[string]string{testAnnotationKey: testAnnotationValue},
},
),
Entry("workspace with nil workspace kind",
func() *kubefloworgv1beta1.Workspace {
return NewWorkspaceBuilder().
WithHomeVolume(testHomePVC).
Build()
},
func() *kubefloworgv1beta1.WorkspaceKind {
return nil
},
TestExpectations{
Name: testWorkspaceName,
Namespace: testNamespace,
WorkspaceKindName: testWorkspaceKindName,
WorkspaceKindMissing: true,
State: workspaces.WorkspaceStateRunning,
StateMessage: testStateMessage,
HomePVCName: testHomePVC,
HomeMountPath: workspaces.UnknownHomeMountPath,
HomeReadOnly: false,
DataVolumeCount: 0,
ServiceCount: 0,
},
),
Entry("workspace kind without UID",
func() *kubefloworgv1beta1.Workspace {
return NewWorkspaceBuilder().
WithHomeVolume(testHomePVC).
Build()
},
func() *kubefloworgv1beta1.WorkspaceKind {
return NewWorkspaceKindBuilder().
WithoutUID().
Build()
},
TestExpectations{
Name: testWorkspaceName,
Namespace: testNamespace,
WorkspaceKindName: testWorkspaceKindName,
WorkspaceKindMissing: true,
State: workspaces.WorkspaceStateRunning,
StateMessage: testStateMessage,
HomePVCName: testHomePVC,
HomeMountPath: workspaces.UnknownHomeMountPath,
HomeReadOnly: false,
DataVolumeCount: 0,
ServiceCount: 0,
},
),
Entry("workspace without home volume",
func() *kubefloworgv1beta1.Workspace {
return NewWorkspaceBuilder().
WithoutHomeVolume().
Build()
},
func() *kubefloworgv1beta1.WorkspaceKind {
return NewWorkspaceKindBuilder().Build()
},
TestExpectations{
Name: testWorkspaceName,
Namespace: testNamespace,
WorkspaceKindName: testWorkspaceKindName,
WorkspaceKindMissing: false,
State: workspaces.WorkspaceStateRunning,
StateMessage: testStateMessage,
HomePVCName: "", // Empty means nil home volume
DataVolumeCount: 0,
ServiceCount: 0,
},
),
)
DescribeTable("should map workspace states correctly",
func(inputState kubefloworgv1beta1.WorkspaceState, expectedState workspaces.WorkspaceState) {
workspace := NewWorkspaceBuilder().WithState(inputState).Build()
workspaceKind := NewWorkspaceKindBuilder().Build()
result := workspaces.NewWorkspaceModelFromWorkspace(workspace, workspaceKind)
Expect(result.State).To(Equal(expectedState))
},
Entry("Running", kubefloworgv1beta1.WorkspaceStateRunning, workspaces.WorkspaceStateRunning),
Entry("Terminating", kubefloworgv1beta1.WorkspaceStateTerminating, workspaces.WorkspaceStateTerminating),
Entry("Paused", kubefloworgv1beta1.WorkspaceStatePaused, workspaces.WorkspaceStatePaused),
Entry("Pending", kubefloworgv1beta1.WorkspaceStatePending, workspaces.WorkspaceStatePending),
Entry("Error", kubefloworgv1beta1.WorkspaceStateError, workspaces.WorkspaceStateError),
Entry("Unknown", kubefloworgv1beta1.WorkspaceStateUnknown, workspaces.WorkspaceStateUnknown),
)
Context("error cases", func() {
It("should panic when workspace kind name doesn't match", func() {
workspace := NewWorkspaceBuilder().Build()
workspaceKind := NewWorkspaceKindBuilder().WithName("different-kind").Build()
Expect(func() {
workspaces.NewWorkspaceModelFromWorkspace(workspace, workspaceKind)
}).To(Panic())
})
})
})
})

View File

@ -0,0 +1,29 @@
/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package workspaces_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestWorkspaces(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Workspaces Suite")
}

View File

@ -27,14 +27,7 @@ func NewHealthCheckRepository() *HealthCheckRepository {
}
func (r *HealthCheckRepository) HealthCheck(version string) (models.HealthCheck, error) {
var res = models.HealthCheck{
// TODO: implement actual health check logic
Status: models.ServiceStatusHealthy,
SystemInfo: models.SystemInfo{
Version: version,
},
}
// TODO: implement actual health check logic
res := models.NewHealthCheck(models.ServiceStatusHealthy, version)
return res, nil
}

View File

@ -0,0 +1,63 @@
/*
Copyright 2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package health_check_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/health_check"
repo "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories"
health_check "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/health_check"
)
func TestHealthCheckRepositories(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "HealthCheck Repositories Suite")
}
var _ = Describe("HealthCheckRepository", func() {
var (
repos *repo.Repositories
healthRepo *health_check.HealthCheckRepository
)
BeforeEach(func() {
repos = repo.NewRepositories(nil)
healthRepo = repos.HealthCheck
})
Context("Repository Initialization", func() {
It("should initialize with non-nil repository", func() {
Expect(repos).ToNot(BeNil())
Expect(healthRepo).ToNot(BeNil())
Expect(healthRepo).To(BeAssignableToTypeOf(&health_check.HealthCheckRepository{}))
})
})
Context("HealthCheck Functionality", func() {
It("should return healthy status with valid version", func() {
version := "1.0.0"
result, err := healthRepo.HealthCheck(version)
Expect(err).ToNot(HaveOccurred())
Expect(result.Status).To(Equal(models.ServiceStatusHealthy))
Expect(result.SystemInfo.Version).To(Equal(version))
})
})
})