2020-09-06 13:20:55 +02:00
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io"
2021-11-03 19:21:26 +01:00
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/sanity-io/litter"
2022-09-23 18:38:02 +02:00
cronjobv1 "k8s.io/api/batch/v1"
2020-09-06 13:20:55 +02:00
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
2022-09-23 18:38:02 +02:00
applyv1 "k8s.io/client-go/applyconfigurations/batch/v1"
2020-09-06 13:20:55 +02:00
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes"
2022-09-23 18:38:02 +02:00
batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1"
2020-09-06 13:20:55 +02:00
"k8s.io/client-go/rest"
)
func Test_Main ( t * testing . T ) {
tests := [ ] struct {
name string
connectFunc func ( provider ConfigProvider ) ( Client , error )
exitFunc func ( code int )
} {
{
name : "error connecting to K8S" ,
connectFunc : func ( ConfigProvider ) ( Client , error ) {
return nil , errors . New ( "error" )
} ,
exitFunc : func ( code int ) {
if code != 1 {
t . Errorf ( "main() got %d, want 1" , code )
}
} ,
} ,
}
for _ , tt := range tests {
os . Args = [ ] string { "dummy" , "--slack-url" , "https://dummy.example.org" }
t . Run ( tt . name , func ( t * testing . T ) {
exitFunc = tt . exitFunc
main ( )
} )
}
}
func Test_doMain ( t * testing . T ) {
type args struct {
slackUrl string
provider ClientProvider
}
tests := [ ] struct {
name string
args args
checkFunc func ( client Client , slackUrl string , ic chan os . Signal , sleepTime time . Duration , out io . Writer ) error
want int
} {
{
name : "error checking" ,
args : args {
provider : & brokenClientProvider { } ,
} ,
checkFunc : func ( client Client , slackUrl string , ic chan os . Signal , sleepTime time . Duration , out io . Writer ) error {
return errors . New ( "error" )
} ,
want : 1 ,
} ,
{
name : "success" ,
args : args {
provider : & brokenClientProvider { } ,
} ,
checkFunc : func ( client Client , slackUrl string , ic chan os . Signal , sleepTime time . Duration , out io . Writer ) error {
return nil
} ,
want : 0 ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
checkFunc = tt . checkFunc
if got := doMain ( tt . args . slackUrl , tt . args . provider ) ; got != tt . want {
t . Errorf ( "doMain() = %v, want %v" , got , tt . want )
}
} )
}
}
func Test_doCheck ( t * testing . T ) {
type args struct {
client Client
}
tests := [ ] struct {
name string
args args
timeout time . Duration
slackResponse string
wantErr bool
wantOut [ ] string
} {
{
name : "error getting cronjobs" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
2020-09-06 13:20:55 +02:00
return nil , errors . New ( "error" )
} ,
} ,
} ,
} ,
} ,
wantErr : true ,
} ,
{
name : "no cronjobs" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList { } , nil
2020-09-06 13:20:55 +02:00
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
wantErr : false ,
} ,
{
name : "suspended cronjobs are ignored" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Suspend : boolP ( true ) } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
wantErr : false ,
} ,
{
name : "invalid cron schedule" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "abc" } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
wantErr : true ,
} ,
{
name : "only correctly running cronjobs" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
ObjectMeta : v1 . ObjectMeta { CreationTimestamp : v1 . Time { Time : time . Now ( ) } } ,
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "* * * * *" , Suspend : boolP ( false ) } ,
2020-09-06 13:20:55 +02:00
} ,
{
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "* * * * *" } ,
Status : cronjobv1 . CronJobStatus { LastScheduleTime : & v1 . Time { Time : time . Now ( ) } } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
wantErr : false ,
} ,
{
name : "error in Slack call" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
ObjectMeta : v1 . ObjectMeta { Name : "some-name" , Namespace : "some-ns" } ,
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "* * * * *" } ,
Status : cronjobv1 . CronJobStatus { LastScheduleTime : & v1 . Time { Time : time . Now ( ) . Add ( - 3 * time . Minute ) } } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
slackResponse : "dummy" ,
wantErr : false ,
wantOut : [ ] string { "Checking some-ns/some-name since" , "some-ns/some-name was not scheduled. Sending Slack notification." , "Unable to send Slack notification: slack: request failed statuscode: 200, message: invalid character 'd' looking for beginning of value" } ,
} ,
{
name : "Slack response not ok" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
ObjectMeta : v1 . ObjectMeta { Name : "some-name" , Namespace : "some-ns" } ,
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "* * * * *" } ,
Status : cronjobv1 . CronJobStatus { LastScheduleTime : & v1 . Time { Time : time . Now ( ) . Add ( - 3 * time . Minute ) } } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
slackResponse : ` { "ok": false, "error": "Something went wrong"} ` ,
wantErr : false ,
wantOut : [ ] string { "Checking some-ns/some-name since" , "some-ns/some-name was not scheduled. Sending Slack notification." , "Unable to send Slack notification: slack: request failed statuscode: 200, message: Something went wrong" } ,
} ,
{
name : "Slack response ok" ,
args : args {
client : & brokenClient {
batchApi : & batchApi {
cronApi : & cronApi {
2022-09-23 18:38:02 +02:00
listFn : func ( _ context . Context , _ v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
return & cronjobv1 . CronJobList {
Items : [ ] cronjobv1 . CronJob {
2020-09-06 13:20:55 +02:00
{
ObjectMeta : v1 . ObjectMeta { Name : "some-name" , Namespace : "some-ns" } ,
2022-09-23 18:38:02 +02:00
Spec : cronjobv1 . CronJobSpec { Schedule : "* * * * *" } ,
Status : cronjobv1 . CronJobStatus { LastScheduleTime : & v1 . Time { Time : time . Now ( ) . Add ( - 3 * time . Minute ) } } ,
2020-09-06 13:20:55 +02:00
} ,
} ,
} , nil
} ,
} ,
} ,
} ,
} ,
timeout : time . Second ,
slackResponse : ` { "ok": true} ` ,
wantErr : false ,
wantOut : [ ] string { "Checking some-ns/some-name since" , "some-ns/some-name was not scheduled. Sending Slack notification." } ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
ic := make ( chan os . Signal , 1 )
if tt . timeout > 0 {
timeout := tt . timeout
fmt . Printf ( "Waiting %s before terminating\n" , timeout . String ( ) )
go func ( ) {
time . Sleep ( timeout )
fmt . Println ( "Done waiting, terminating" )
ic <- os . Interrupt
} ( )
}
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
_ , _ = w . Write ( [ ] byte ( tt . slackResponse ) )
} ) )
defer server . Close ( )
baseURL := server . Listener . Addr ( ) . String ( )
buff := & bytes . Buffer { }
if err := doCheck ( tt . args . client , fmt . Sprintf ( "http://%s" , baseURL ) , ic , 10 * time . Millisecond , buff ) ; ( err != nil ) != tt . wantErr {
t . Errorf ( "doCheck() error = %v, wantErr %v" , err , tt . wantErr )
}
if len ( tt . wantOut ) > 0 {
for _ , o := range tt . wantOut {
if ! strings . Contains ( buff . String ( ) , o ) {
t . Errorf ( "doCheck() got %s, want %s" , buff . String ( ) , o )
}
}
}
} )
}
}
func TestDefaultProvider_Provide ( t * testing . T ) {
type fields struct {
provider ConfigProvider
}
tests := [ ] struct {
name string
fields fields
want Client
wantErr bool
} {
{
name : "not in cluster" ,
fields : fields { provider : & InClusterProvider { } } ,
want : nil ,
wantErr : true ,
} ,
{
name : "dummy config" ,
fields : fields { provider : & dummyProvider { } } ,
want : & kubernetes . Clientset {
DiscoveryClient : & discovery . DiscoveryClient {
LegacyPrefix : "/api" ,
} ,
} ,
wantErr : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
d := DefaultProvider {
provider : tt . fields . provider ,
}
got , err := d . Provide ( )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "Provide() error = %v, wantErr %v" , err , tt . wantErr )
return
}
gotDump := litter . Sdump ( got )
wantDump := litter . Sdump ( tt . want )
if gotDump != wantDump {
t . Errorf ( "Provide() got = %v, want %v" , gotDump , wantDump )
}
} )
}
}
type dummyProvider struct { }
func ( d dummyProvider ) Provide ( ) ( * rest . Config , error ) {
return & rest . Config { } , nil
}
var _ ConfigProvider = & dummyProvider { }
type brokenClientProvider struct { }
func ( b brokenClientProvider ) Provide ( ) ( Client , error ) {
return & brokenClient { } , nil
}
var _ ClientProvider = & brokenClientProvider { }
type brokenClient struct {
2022-09-23 18:38:02 +02:00
batchApi batchv1 . BatchV1Interface
2020-09-06 13:20:55 +02:00
}
2022-09-23 18:38:02 +02:00
func ( b brokenClient ) BatchV1 ( ) batchv1 . BatchV1Interface {
2020-09-06 13:20:55 +02:00
return b . batchApi
}
var _ Client = & brokenClient { }
type batchApi struct {
2022-09-23 18:38:02 +02:00
cronApi batchv1 . CronJobInterface
2020-09-06 13:20:55 +02:00
}
func ( b batchApi ) RESTClient ( ) rest . Interface {
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( b batchApi ) CronJobs ( namespace string ) batchv1 . CronJobInterface {
2020-09-06 13:20:55 +02:00
return b . cronApi
}
2022-09-23 18:38:02 +02:00
func ( b batchApi ) Jobs ( namespace string ) batchv1 . JobInterface {
//TODO implement me
panic ( "implement me" )
}
var _ batchv1 . BatchV1Interface = & batchApi { }
2020-09-06 13:20:55 +02:00
type cronApi struct {
2022-09-23 18:38:02 +02:00
listFn func ( ctx context . Context , opts v1 . ListOptions ) ( * cronjobv1 . CronJobList , error )
2020-09-06 13:20:55 +02:00
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) List ( ctx context . Context , opts v1 . ListOptions ) ( * cronjobv1 . CronJobList , error ) {
2020-09-06 13:20:55 +02:00
return c . listFn ( ctx , opts )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) Create ( ctx context . Context , cronJob * cronjobv1 . CronJob , opts v1 . CreateOptions ) ( * cronjobv1 . CronJob , error ) {
2020-09-06 13:20:55 +02:00
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) Update ( ctx context . Context , cronJob * cronjobv1 . CronJob , opts v1 . UpdateOptions ) ( * cronjobv1 . CronJob , error ) {
2020-09-06 13:20:55 +02:00
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) UpdateStatus ( ctx context . Context , cronJob * cronjobv1 . CronJob , opts v1 . UpdateOptions ) ( * cronjobv1 . CronJob , error ) {
2020-09-06 13:20:55 +02:00
panic ( "implement me" )
}
func ( c cronApi ) Delete ( ctx context . Context , name string , opts v1 . DeleteOptions ) error {
panic ( "implement me" )
}
func ( c cronApi ) DeleteCollection ( ctx context . Context , opts v1 . DeleteOptions , listOpts v1 . ListOptions ) error {
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) Get ( ctx context . Context , name string , opts v1 . GetOptions ) ( * cronjobv1 . CronJob , error ) {
2020-09-06 13:20:55 +02:00
panic ( "implement me" )
}
func ( c cronApi ) Watch ( ctx context . Context , opts v1 . ListOptions ) ( watch . Interface , error ) {
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) Patch ( ctx context . Context , name string , pt types . PatchType , data [ ] byte , opts v1 . PatchOptions , subresources ... string ) ( result * cronjobv1 . CronJob , err error ) {
2020-09-06 13:20:55 +02:00
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) Apply ( ctx context . Context , cronJob * applyv1 . CronJobApplyConfiguration , opts v1 . ApplyOptions ) ( result * cronjobv1 . CronJob , err error ) {
2021-11-03 19:21:26 +01:00
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
func ( c cronApi ) ApplyStatus ( ctx context . Context , cronJob * applyv1 . CronJobApplyConfiguration , opts v1 . ApplyOptions ) ( result * cronjobv1 . CronJob , err error ) {
2021-11-03 19:21:26 +01:00
panic ( "implement me" )
}
2022-09-23 18:38:02 +02:00
var _ batchv1 . CronJobInterface = & cronApi { }
2020-09-06 13:20:55 +02:00
func boolP ( b bool ) * bool {
return & b
}