github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/api_test.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"slices"
     9  	"sort"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/dashboard/dashapi"
    14  	"github.com/google/syzkaller/sys/targets"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  func TestClientSecretOK(t *testing.T) {
    19  	got, err := checkClient(&GlobalConfig{
    20  		Clients: map[string]string{
    21  			"user": "secr1t",
    22  		},
    23  	}, "user", "secr1t", "")
    24  	if err != nil || got != "" {
    25  		t.Errorf("unexpected error %v %v", got, err)
    26  	}
    27  }
    28  
    29  func TestClientOauthOK(t *testing.T) {
    30  	got, err := checkClient(&GlobalConfig{
    31  		Clients: map[string]string{
    32  			"user": "OauthSubject:public",
    33  		},
    34  	}, "user", "", "OauthSubject:public")
    35  	if err != nil || got != "" {
    36  		t.Errorf("unexpected error %v %v", got, err)
    37  	}
    38  }
    39  
    40  func TestClientSecretFail(t *testing.T) {
    41  	got, err := checkClient(&GlobalConfig{
    42  		Clients: map[string]string{
    43  			"user": "secr1t",
    44  		},
    45  	}, "user", "wrong", "")
    46  	if err != ErrAccess || got != "" {
    47  		t.Errorf("unexpected error %v %v", got, err)
    48  	}
    49  }
    50  
    51  func TestClientSecretMissing(t *testing.T) {
    52  	got, err := checkClient(&GlobalConfig{
    53  		Clients: map[string]string{},
    54  	}, "user", "ignored", "")
    55  	if err != ErrAccess || got != "" {
    56  		t.Errorf("unexpected error %v %v", got, err)
    57  	}
    58  }
    59  
    60  func TestClientNamespaceOK(t *testing.T) {
    61  	got, err := checkClient(&GlobalConfig{
    62  		Namespaces: map[string]*Config{
    63  			"ns1": {
    64  				Clients: map[string]string{
    65  					"user": "secr1t",
    66  				},
    67  			},
    68  		},
    69  	}, "user", "secr1t", "")
    70  	if err != nil || got != "ns1" {
    71  		t.Errorf("unexpected error %v %v", got, err)
    72  	}
    73  }
    74  
    75  func TestEmergentlyStoppedEmail(t *testing.T) {
    76  	c := NewCtx(t)
    77  	defer c.Close()
    78  
    79  	client := c.publicClient
    80  	build := testBuild(1)
    81  	client.UploadBuild(build)
    82  
    83  	crash := testCrash(build, 1)
    84  	client.ReportCrash(crash)
    85  
    86  	c.advanceTime(time.Hour)
    87  	_, err := c.AuthGET(AccessAdmin, "/admin?action=emergency_stop")
    88  	c.expectOK(err)
    89  
    90  	// There should be no email.
    91  	c.advanceTime(time.Hour)
    92  	c.expectNoEmail()
    93  }
    94  
    95  func TestEmergentlyStoppedReproEmail(t *testing.T) {
    96  	c := NewCtx(t)
    97  	defer c.Close()
    98  
    99  	client := c.publicClient
   100  	build := testBuild(1)
   101  	client.UploadBuild(build)
   102  
   103  	crash := testCrash(build, 1)
   104  	client.ReportCrash(crash)
   105  	c.pollEmailBug()
   106  
   107  	crash2 := testCrash(build, 1)
   108  	crash2.ReproOpts = []byte("repro opts")
   109  	crash2.ReproSyz = []byte("getpid()")
   110  	client.ReportCrash(crash2)
   111  
   112  	c.advanceTime(time.Hour)
   113  	_, err := c.AuthGET(AccessAdmin, "/admin?action=emergency_stop")
   114  	c.expectOK(err)
   115  
   116  	// There should be no email.
   117  	c.advanceTime(time.Hour)
   118  	c.expectNoEmail()
   119  }
   120  
   121  func TestEmergentlyStoppedExternalReport(t *testing.T) {
   122  	c := NewCtx(t)
   123  	defer c.Close()
   124  
   125  	client := c.client
   126  	build := testBuild(1)
   127  	client.UploadBuild(build)
   128  
   129  	crash := testCrash(build, 1)
   130  	client.ReportCrash(crash)
   131  
   132  	c.advanceTime(time.Hour)
   133  	_, err := c.AuthGET(AccessAdmin, "/admin?action=emergency_stop")
   134  	c.expectOK(err)
   135  
   136  	// There should be no email.
   137  	c.advanceTime(time.Hour)
   138  	client.pollBugs(0)
   139  }
   140  
   141  func TestEmergentlyStoppedEmailJob(t *testing.T) {
   142  	c := NewCtx(t)
   143  	defer c.Close()
   144  
   145  	client := c.publicClient
   146  	build := testBuild(1)
   147  	client.UploadBuild(build)
   148  
   149  	crash := testCrash(build, 1)
   150  	crash.ReproOpts = []byte("repro opts")
   151  	crash.ReproSyz = []byte("getpid()")
   152  	client.ReportCrash(crash)
   153  	sender := c.pollEmailBug().Sender
   154  	c.incomingEmail(sender, "#syz upstream\n")
   155  	sender = c.pollEmailBug().Sender
   156  
   157  	// Send a patch testing request.
   158  	c.advanceTime(time.Hour)
   159  	c.incomingEmail(sender, syzTestGitBranchSamplePatch,
   160  		EmailOptMessageID(1), EmailOptFrom("test@requester.com"),
   161  		EmailOptCC([]string{"somebody@else.com", "test@syzkaller.com"}))
   162  	c.expectNoEmail()
   163  
   164  	// Emulate a finished job.
   165  	pollResp := client.pollJobs(build.Manager)
   166  	c.expectEQ(pollResp.Type, dashapi.JobTestPatch)
   167  
   168  	c.advanceTime(time.Hour)
   169  	jobDoneReq := &dashapi.JobDoneReq{
   170  		ID:          pollResp.ID,
   171  		Build:       *build,
   172  		CrashTitle:  "test crash title",
   173  		CrashLog:    []byte("test crash log"),
   174  		CrashReport: []byte("test crash report"),
   175  	}
   176  	client.JobDone(jobDoneReq)
   177  
   178  	// Now we emergently stop syzbot.
   179  	c.advanceTime(time.Hour)
   180  	_, err := c.AuthGET(AccessAdmin, "/admin?action=emergency_stop")
   181  	c.expectOK(err)
   182  
   183  	// There should be no email.
   184  	c.advanceTime(time.Hour)
   185  	c.expectNoEmail()
   186  }
   187  
   188  func TestEmergentlyStoppedCrashReport(t *testing.T) {
   189  	c := NewCtx(t)
   190  	defer c.Close()
   191  
   192  	client := c.publicClient
   193  	build := testBuild(1)
   194  	client.UploadBuild(build)
   195  
   196  	// Now we emergently stop syzbot.
   197  	c.advanceTime(time.Hour)
   198  	_, err := c.AuthGET(AccessAdmin, "/admin?action=emergency_stop")
   199  	c.expectOK(err)
   200  
   201  	crash := testCrash(build, 1)
   202  	crash.ReproOpts = []byte("repro opts")
   203  	crash.ReproSyz = []byte("getpid()")
   204  	client.ReportCrash(crash)
   205  
   206  	listResp, err := client.BugList()
   207  	c.expectOK(err)
   208  	c.expectEQ(len(listResp.List), 0)
   209  }
   210  
   211  func TestUpdateReportingPriority(t *testing.T) {
   212  	bug := &Bug{
   213  		Namespace: testConfig.DefaultNamespace,
   214  		Title:     "bug",
   215  	}
   216  	build := Build{
   217  		Namespace:    testConfig.DefaultNamespace,
   218  		KernelRepo:   "git://syzkaller.org",
   219  		KernelBranch: "branch10",
   220  	}
   221  
   222  	crashes := []*Crash{
   223  		// This group of crashes should have the same priority.
   224  		// Revoked and no repro.
   225  		{
   226  			BuildID:        "0",
   227  			ReproIsRevoked: true,
   228  		},
   229  		// Non-revoked and no repro.
   230  		{
   231  			BuildID: "1",
   232  		},
   233  		// Revoked but has syz repro.
   234  		{
   235  			BuildID:        "2",
   236  			ReproIsRevoked: true,
   237  			ReproSyz:       1,
   238  		},
   239  		// Revoked but has C repro.
   240  		{
   241  			BuildID:        "3",
   242  			ReproIsRevoked: true,
   243  			ReproC:         1,
   244  		},
   245  
   246  		// This group of crashes should have the same priority.
   247  		// Non-revoked and no repro but title matches with bug.
   248  		{
   249  			BuildID: "4",
   250  			Title:   bug.Title,
   251  		},
   252  		// Revoked and has C repro and title matches with bug.
   253  		{
   254  			BuildID:        "5",
   255  			ReproC:         1,
   256  			Title:          bug.Title,
   257  			ReproIsRevoked: true,
   258  		},
   259  
   260  		// Non-revoked and has syz repro.
   261  		{
   262  			BuildID:  "6",
   263  			ReproSyz: 1,
   264  		},
   265  		// Non-revoked and has C repro.
   266  		{
   267  			BuildID: "7",
   268  			ReproC:  1,
   269  		},
   270  		// Non-revoked and has C repro and title matches with bug.
   271  		{
   272  			BuildID: "8",
   273  			ReproC:  1,
   274  			Title:   bug.Title,
   275  		},
   276  		// Last. Non-revoked, has C repro, title matches with bug and arch is AMD64.
   277  		{
   278  			BuildID: "9",
   279  			ReproC:  1,
   280  			Title:   bug.Title,
   281  		},
   282  	}
   283  
   284  	ctx := context.Background()
   285  	for i, crash := range crashes {
   286  		crash.Manager = "special-obsoleting"
   287  		if i == len(crashes)-1 {
   288  			build.Arch = targets.AMD64
   289  		}
   290  		crash.UpdateReportingPriority(ctx, &build, bug)
   291  	}
   292  
   293  	assert.True(t, sort.SliceIsSorted(crashes, func(i, j int) bool {
   294  		return crashes[i].BuildID < crashes[j].BuildID
   295  	}))
   296  
   297  	var prios []int64
   298  	for _, crash := range crashes {
   299  		prios = append(prios, crash.ReportLen)
   300  	}
   301  
   302  	// "0-3", "4-5" have the same priority (repro revoked as no repro).
   303  	assert.Equal(t, len(slices.Compact(prios)), len(prios)-4)
   304  }
   305  
   306  func TestCreateUploadURL(t *testing.T) {
   307  	c := NewCtx(t)
   308  	defer c.Close()
   309  
   310  	c.transformContext = func(c context.Context) context.Context {
   311  		newConfig := *getConfig(c)
   312  		newConfig.UploadBucket = "blobstorage"
   313  		return contextWithConfig(c, &newConfig)
   314  	}
   315  
   316  	url, err := c.client.CreateUploadURL()
   317  	assert.NoError(t, err)
   318  	assert.Regexp(t, "blobstorage/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}.upload", url)
   319  }