Skip to content

Commit 910d225

Browse files
author
Liav Weiss (EXT-Nokia)
committed
feat(ws): Ensure test files exist in backend for any executed code #381
Signed-off-by: Liav Weiss (EXT-Nokia) <[email protected]>
1 parent b2bee1d commit 910d225

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
Copyright 2024.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package server_test
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"log/slog"
23+
"net"
24+
"strings"
25+
"testing"
26+
"time"
27+
28+
. "github.com/onsi/ginkgo/v2"
29+
. "github.com/onsi/gomega"
30+
31+
"github.com/kubeflow/notebooks/workspaces/backend/api"
32+
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
33+
"github.com/kubeflow/notebooks/workspaces/backend/internal/server"
34+
)
35+
36+
const (
37+
serverStartupTimeout = 5 * time.Second
38+
serverShutdownTimeout = 2 * time.Second
39+
pollInterval = 100 * time.Millisecond
40+
dialTimeout = 500 * time.Millisecond
41+
)
42+
43+
func TestServer(t *testing.T) {
44+
RegisterFailHandler(Fail)
45+
RunSpecs(t, "Server Suite")
46+
}
47+
48+
var _ = Describe("Server Component", func() {
49+
var (
50+
testServer *server.Server
51+
testApp *api.App
52+
testLogger *slog.Logger
53+
ctx context.Context
54+
cancel context.CancelFunc
55+
testPort int
56+
err error
57+
)
58+
59+
// findFreePort is a helper to get an available TCP port, preventing test conflicts.
60+
findFreePort := func() (int, error) {
61+
listener, err := net.Listen("tcp", "localhost:0")
62+
if err != nil {
63+
return 0, err
64+
}
65+
defer listener.Close()
66+
return listener.Addr().(*net.TCPAddr).Port, nil
67+
}
68+
69+
BeforeEach(func() {
70+
ctx, cancel = context.WithCancel(context.Background())
71+
testLogger = slog.New(slog.NewTextHandler(GinkgoWriter, nil))
72+
testPort, err = findFreePort()
73+
Expect(err).NotTo(HaveOccurred(), "failed to find a free port for the test server")
74+
75+
// Create a minimal App config. Disabling auth is key for this simple unit test.
76+
appConfig := &config.EnvConfig{
77+
Port: testPort,
78+
DisableAuth: true,
79+
}
80+
81+
// Create the minimal App instance needed by the server.
82+
// We pass 'nil' for Kubernetes dependencies because they are not needed for this test.
83+
testApp, err = api.NewApp(appConfig, testLogger, nil, nil, nil, nil)
84+
Expect(err).NotTo(HaveOccurred())
85+
})
86+
87+
AfterEach(func() {
88+
cancel()
89+
})
90+
91+
Context("when managing the server lifecycle", func() {
92+
It("should start, listen on the correct port, and shut down gracefully", func() {
93+
var err error
94+
By("creating a new server instance")
95+
testServer, err = server.NewServer(testApp, testLogger)
96+
Expect(err).NotTo(HaveOccurred())
97+
Expect(testServer).NotTo(BeNil())
98+
99+
serverErrChan := make(chan error, 1)
100+
By("starting the server in a background goroutine")
101+
go func() {
102+
defer GinkgoRecover()
103+
serverErrChan <- testServer.Start(ctx)
104+
}()
105+
106+
serverAddr := fmt.Sprintf("localhost:%d", testPort)
107+
By("verifying the server is listening on " + serverAddr)
108+
// Eventually checks that the TCP port becomes available.
109+
Eventually(func() error {
110+
conn, err := net.DialTimeout("tcp", serverAddr, dialTimeout)
111+
if err != nil {
112+
return err
113+
}
114+
conn.Close()
115+
return nil
116+
}, serverStartupTimeout, pollInterval).Should(Succeed())
117+
118+
By("triggering a graceful shutdown")
119+
cancel()
120+
121+
By("verifying the server's Start method returns nil for a clean shutdown")
122+
Eventually(serverErrChan, serverStartupTimeout).Should(Receive(BeNil()))
123+
124+
By("verifying the server is no longer listening")
125+
// Consistently checks that the port remains closed and specifically for connection refused.
126+
Consistently(func() error {
127+
_, err := net.DialTimeout("tcp", serverAddr, dialTimeout)
128+
return err
129+
}, serverShutdownTimeout, pollInterval).Should(
130+
WithTransform(func(e error) bool { // Transform the error into a boolean for assertion
131+
if e == nil {
132+
return false // If error is nil, connection succeeded, which is NOT desired.
133+
}
134+
return strings.Contains(e.Error(), "connection refused") ||
135+
strings.Contains(e.Error(), "dial tcp")
136+
}, BeTrue()), "Server port should be closed after shutdown and return a connection refused error",
137+
)
138+
})
139+
})
140+
})

0 commit comments

Comments
 (0)