renter_test.go 56.7 KB
Newer Older
Luke Champine's avatar
Luke Champine committed
1 2 3
package api

import (
4 5
	"bytes"
	"fmt"
6
	"io"
Luke Champine's avatar
Luke Champine committed
7 8
	"io/ioutil"
	"net/url"
9
	"os"
Luke Champine's avatar
Luke Champine committed
10
	"path/filepath"
11 12
	"strconv"
	"strings"
Luke Champine's avatar
Luke Champine committed
13
	"testing"
14
	"time"
Luke Champine's avatar
Luke Champine committed
15

16 17 18
	"gitlab.com/NebulousLabs/Sia/build"
	"gitlab.com/NebulousLabs/Sia/modules"
	"gitlab.com/NebulousLabs/Sia/modules/renter/contractor"
19
	"gitlab.com/NebulousLabs/Sia/modules/renter/siafile"
20
	"gitlab.com/NebulousLabs/Sia/types"
21 22

	"gitlab.com/NebulousLabs/errors"
23
	"gitlab.com/NebulousLabs/fastrand"
24 25 26
)

const (
27 28 29
	testFunds       = "10000000000000000000000000000" // 10k SC
	testPeriod      = "5"
	testRenewWindow = "2"
Luke Champine's avatar
Luke Champine committed
30 31
)

Luke Champine's avatar
Luke Champine committed
32
// createRandFile creates a file on disk and fills it with random bytes.
33
func createRandFile(path string, size int) error {
34
	return ioutil.WriteFile(path, fastrand.Bytes(size), 0600)
Luke Champine's avatar
Luke Champine committed
35 36
}

Ava Howell's avatar
Ava Howell committed
37 38
// setupTestDownload creates a server tester with an uploaded file of size
// `size` and name `name`.
39
func setupTestDownload(t *testing.T, size int, name string, waitOnRedundancy bool) (*serverTester, string) {
40
	st, err := createServerTester(t.Name())
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
	if err != nil {
		t.Fatal(err)
	}

	// Announce the host and start accepting contracts.
	err = st.announceHost()
	if err != nil {
		t.Fatal(err)
	}
	err = st.acceptContracts()
	if err != nil {
		t.Fatal(err)
	}
	err = st.setHostStorage()
	if err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
61
	testFunds := testFunds
62
	testPeriod := "10"
63
	renewWindow := "5"
64 65
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
66
	allowanceValues.Set("renewwindow", renewWindow)
67
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
68 69 70 71 72 73
	err = st.stdPostAPI("/renter", allowanceValues)
	if err != nil {
		t.Fatal(err)
	}

	// Create a file.
74 75
	path := filepath.Join(build.SiaTestingDir, "api", t.Name(), name)
	err = createRandFile(path, size)
76 77 78 79 80 81 82 83
	if err != nil {
		t.Fatal(err)
	}

	// Upload to host.
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
	uploadValues.Set("renew", "true")
84 85
	uploadValues.Set("datapieces", "1")
	uploadValues.Set("paritypieces", "1")
86
	err = st.stdPostAPI("/renter/upload/"+name, uploadValues)
87 88 89 90
	if err != nil {
		t.Fatal(err)
	}

91 92
	if waitOnRedundancy {
		// wait for the file to have a redundancy > 1
93
		err = build.Retry(200, time.Second, func() error {
94
			var rf RenterFiles
95
			st.getAPI("/renter/files", &rf)
96
			if len(rf.Files) != 1 || rf.Files[0].Redundancy < 1 {
97
				return fmt.Errorf("the uploading is not succeeding for some reason: %v", rf.Files[0])
98 99 100 101 102
			}
			return nil
		})
		if err != nil {
			t.Fatal(err)
103 104 105 106 107 108
		}
	}

	return st, path
}

109 110 111 112
// runDownloadTest uploads a file and downloads it using the specified
// parameters, verifying that the parameters are applied correctly and the file
// is downloaded successfully.
func runDownloadTest(t *testing.T, filesize, offset, length int64, useHttpResp bool, testName string) error {
113 114 115 116 117
	ulSiaPath, err := modules.NewSiaPath(testName + ".dat")
	if err != nil {
		t.Fatal(err)
	}
	st, path := setupTestDownload(t, int(filesize), ulSiaPath.String(), true)
118 119 120 121
	defer func() {
		st.server.panicClose()
		os.Remove(path)
	}()
122 123

	// Read the section to be downloaded from the original file.
124 125
	uf, err := os.Open(path) // Uploaded file.
	if err != nil {
126
		return errors.AddContext(err, "unable to open the uploaded file locally")
127 128 129 130
	}
	var originalBytes bytes.Buffer
	_, err = uf.Seek(offset, 0)
	if err != nil {
131
		return errors.AddContext(err, "error when seeking through the local uploaded file")
132 133 134
	}
	_, err = io.CopyN(&originalBytes, uf, length)
	if err != nil {
135
		return errors.AddContext(err, "error when copying from the local uploaded file")
136
	}
137

138
	// Download the original file from the passed offsets.
139
	fname := testName + "-download.dat"
140
	downpath := filepath.Join(st.dir, fname)
141 142
	defer os.Remove(downpath)

143 144
	dlURL := fmt.Sprintf("/renter/download/%s?offset=%d&length=%d", ulSiaPath, offset, length)

145
	var downbytes bytes.Buffer
146 147 148 149 150 151

	if useHttpResp {
		dlURL += "&httpresp=true"
		// Make request.
		resp, err := HttpGET("http://" + st.server.listener.Addr().String() + dlURL)
		if err != nil {
152
			return errors.AddContext(err, "unable to make an http request")
153
		}
154 155
		defer resp.Body.Close()

156 157
		_, err = io.Copy(&downbytes, resp.Body)
		if err != nil {
158
			return errors.AddContext(err, "unable to make a copy after the http request")
159 160 161 162 163
		}
	} else {
		dlURL += "&destination=" + downpath
		err := st.getAPI(dlURL, nil)
		if err != nil {
164
			return errors.AddContext(err, "download request failed")
165
		}
Ava Howell's avatar
Ava Howell committed
166
		// wait for the download to complete
167
		err = build.Retry(30, time.Second, func() error {
Ava Howell's avatar
Ava Howell committed
168 169 170
			var rdq RenterDownloadQueue
			err = st.getAPI("/renter/downloads", &rdq)
			if err != nil {
171
				return errors.AddContext(err, "unable to view the download queue")
Ava Howell's avatar
Ava Howell committed
172 173
			}
			for _, download := range rdq.Downloads {
174
				if download.Received == download.Filesize && download.SiaPath.Equals(ulSiaPath) {
Ava Howell's avatar
Ava Howell committed
175 176 177 178 179 180
					return nil
				}
			}
			return errors.New("file not downloaded")
		})
		if err != nil {
181
			t.Fatal(errors.AddContext(err, "download does not appear to have completed"))
Ava Howell's avatar
Ava Howell committed
182
		}
183

Ava Howell's avatar
Ava Howell committed
184 185
		// open the downloaded file
		df, err := os.Open(downpath)
186 187 188 189 190 191 192 193
		if err != nil {
			return err
		}
		defer df.Close()

		_, err = io.Copy(&downbytes, df)
		if err != nil {
			return err
194 195 196
		}
	}

197 198
	// should have correct length
	if int64(downbytes.Len()) != length {
mikkeljuhl's avatar
mikkeljuhl committed
199
		return fmt.Errorf("downloaded file has incorrect size: %d, %d expected", downbytes.Len(), length)
200 201 202
	}

	// should be byte-for-byte equal to the original uploaded file
Ava Howell's avatar
Ava Howell committed
203
	if !bytes.Equal(originalBytes.Bytes(), downbytes.Bytes()) {
mikkeljuhl's avatar
mikkeljuhl committed
204
		return fmt.Errorf("downloaded content differs from original content")
205
	}
206 207

	return nil
208 209
}

210 211
// TestRenterDownloadError tests that the /renter/download route sets the
// download's error field if it fails.
212 213 214 215 216 217 218 219 220
func TestRenterDownloadError(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	st, _ := setupTestDownload(t, 1e4, "test.dat", false)
	defer st.server.Close()

221 222
	// don't wait for the upload to complete, try to download immediately to
	// intentionally cause a download error
223 224 225 226 227 228 229 230
	downpath := filepath.Join(st.dir, "down.dat")
	expectedErr := st.getAPI("/renter/download/test.dat?destination="+downpath, nil)
	if expectedErr == nil {
		t.Fatal("download unexpectedly succeeded")
	}

	// verify the file has the expected error
	var rdq RenterDownloadQueue
231
	err := st.getAPI("/renter/downloads", &rdq)
232 233 234 235
	if err != nil {
		t.Fatal(err)
	}
	for _, download := range rdq.Downloads {
236
		if download.SiaPath.String() == "test.dat" && download.Received == download.Filesize && download.Error == expectedErr.Error() {
237 238 239 240 241
			t.Fatal("download had unexpected error: ", download.Error)
		}
	}
}

242 243
// TestValidDownloads tests valid and boundary parameter combinations.
func TestValidDownloads(t *testing.T) {
244 245 246 247 248
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

Ava Howell's avatar
Ava Howell committed
249
	sectorSize := int64(modules.SectorSize)
250 251 252 253 254 255 256 257

	testParams := []struct {
		filesize,
		offset,
		length int64
		useHttpResp bool
		testName    string
	}{
Ava Howell's avatar
Ava Howell committed
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
		// file-backed tests.
		{sectorSize, 40, sectorSize - 40, false, "OffsetSingleChunk"},
		{sectorSize * 2, 20, sectorSize*2 - 20, false, "OffsetTwoChunk"},
		{int64(float64(sectorSize) * 2.4), 20, int64(float64(sectorSize)*2.4) - 20, false, "OffsetThreeChunk"},
		{sectorSize, 0, sectorSize / 2, false, "ShortLengthSingleChunk"},
		{sectorSize, sectorSize / 4, sectorSize / 2, false, "ShortLengthAndOffsetSingleChunk"},
		{sectorSize * 2, 0, int64(float64(sectorSize) * 2 * 0.75), false, "ShortLengthTwoChunk"},
		{int64(float64(sectorSize) * 2.7), 0, int64(2.2 * float64(sectorSize)), false, "ShortLengthThreeChunkInThirdChunk"},
		{int64(float64(sectorSize) * 2.7), 0, int64(1.6 * float64(sectorSize)), false, "ShortLengthThreeChunkInSecondChunk"},
		{sectorSize * 5, 0, int64(float64(sectorSize*5) * 0.75), false, "ShortLengthMultiChunk"},
		{sectorSize * 2, 50, int64(float64(sectorSize*2) * 0.75), false, "ShortLengthAndOffsetTwoChunk"},
		{sectorSize * 3, 50, int64(float64(sectorSize*3) * 0.5), false, "ShortLengthAndOffsetThreeChunkInSecondChunk"},
		{sectorSize * 3, 50, int64(float64(sectorSize*3) * 0.75), false, "ShortLengthAndOffsetThreeChunkInThirdChunk"},

		// http response tests.
		{sectorSize, 40, sectorSize - 40, true, "HttpRespOffsetSingleChunk"},
		{sectorSize * 2, 40, sectorSize*2 - 40, true, "HttpRespOffsetTwoChunk"},
		{sectorSize * 5, 40, sectorSize*5 - 40, true, "HttpRespOffsetManyChunks"},
		{sectorSize, 40, 4 * sectorSize / 5, true, "RespOffsetAndLengthSingleChunk"},
		{sectorSize * 2, 80, 3 * (sectorSize * 2) / 4, true, "RespOffsetAndLengthTwoChunk"},
		{sectorSize * 5, 150, 3 * (sectorSize * 5) / 4, true, "HttpRespOffsetAndLengthManyChunks"},
		{sectorSize * 5, 150, sectorSize * 5 / 4, true, "HttpRespOffsetAndLengthManyChunksSubsetOfChunks"},
280
	}
281 282
	for _, params := range testParams {
		t.Run(params.testName, func(st *testing.T) {
283 284 285 286 287 288
			st.Parallel()
			err := runDownloadTest(st, params.filesize, params.offset, params.length, params.useHttpResp, params.testName)
			if err != nil {
				st.Fatal(err)
			}
		})
289 290 291
	}
}

292
func runDownloadParamTest(t *testing.T, length, offset, filesize int) error {
293 294
	ulSiaPath := "test.dat"

295 296
	st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true)
	defer st.server.Close()
297 298 299 300

	// Download the original file from offset 40 and length 10.
	fname := "offsetsinglechunk.dat"
	downpath := filepath.Join(st.dir, fname)
301
	dlURL := fmt.Sprintf("/renter/download/%s?destination=%s", ulSiaPath, downpath)
302 303
	dlURL += fmt.Sprintf("&length=%d", length)
	dlURL += fmt.Sprintf("&offset=%d", offset)
304 305 306
	return st.getAPI(dlURL, nil)
}

307
func TestInvalidDownloadParameters(t *testing.T) {
308
	if testing.Short() || !build.VLONG {
309
		t.SkipNow()
310
	}
311
	t.Parallel()
312

313
	testParams := []struct {
314 315 316 317
		length   int
		offset   int
		filesize int
		errorMsg string
318
	}{
319 320 321 322 323
		{0, -10, 1e4, "/download not prompting error when passing negative offset."},
		{0, 1e4, 1e4, "/download not prompting error when passing offset equal to filesize."},
		{1e4 + 1, 0, 1e4, "/download not prompting error when passing length exceeding filesize."},
		{1e4 + 11, 10, 1e4, "/download not prompting error when passing length exceeding filesize with non-zero offset."},
		{-1, 0, 1e4, "/download not prompting error when passing negative length."},
324 325 326
	}

	for _, params := range testParams {
327
		err := runDownloadParamTest(t, params.length, params.offset, params.filesize)
328 329 330
		if err == nil {
			t.Fatal(params.errorMsg)
		}
331
	}
332
}
333

334 335 336
func TestRenterDownloadAsyncAndHttpRespError(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
337
	}
338
	t.Parallel()
339

340 341
	filesize := 1e4
	ulSiaPath := "test.dat"
342

343 344
	st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true)
	defer st.server.Close()
345

346 347 348 349
	// Download the original file from offset 40 and length 10.
	fname := "offsetsinglechunk.dat"
	dlURL := fmt.Sprintf("/renter/download/%s?destination=%s&async=true&httpresp=true", ulSiaPath, fname)
	err := st.getAPI(dlURL, nil)
350
	if err == nil {
351
		t.Fatalf("/download not prompting error when only passing both async and httpresp fields.")
352 353 354
	}
}

355 356 357 358 359 360 361 362 363 364 365 366 367 368
func TestRenterDownloadAsyncNonexistentFile(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	st, err := createServerTester(t.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.Close()

	downpath := filepath.Join(st.dir, "testfile")
	err = st.getAPI(fmt.Sprintf("/renter/downloadasync/doesntexist?destination=%v", downpath), nil)
369 370
	if err == nil {
		t.Error("should not be able to download a file that does not exist")
371 372 373
	}
}

374 375 376
func TestRenterDownloadAsyncAndNotDestinationError(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
377
	}
378
	t.Parallel()
379

380 381
	filesize := 1e4
	ulSiaPath := "test.dat"
382

383 384
	st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true)
	defer st.server.Close()
385

386 387 388
	// Download the original file from offset 40 and length 10.
	dlURL := fmt.Sprintf("/renter/download/%s?async=true", ulSiaPath)
	err := st.getAPI(dlURL, nil)
389
	if err == nil {
390
		t.Fatal("/download not prompting error when async is specified but destination is empty.")
391
	}
392 393
}

394
func TestRenterDownloadHttpRespAndDestinationError(t *testing.T) {
395 396 397 398 399 400 401 402 403 404
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

	filesize := 1e4
	ulSiaPath := "test.dat"

	st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true)
	defer st.server.Close()
405

406
	// Download the original file from offset 40 and length 10.
407 408
	fname := "test.dat"
	dlURL := fmt.Sprintf("/renter/download/%s?destination=%shttpresp=true", ulSiaPath, fname)
409 410
	err := st.getAPI(dlURL, nil)
	if err == nil {
411
		t.Fatal("/download not prompting error when httpresp is specified and destination is non-empty.")
412
	}
413 414
}

Ava Howell's avatar
Ava Howell committed
415 416
// TestRenterAsyncDownloadError tests that the /renter/asyncdownload route sets
// the download's error field if it fails.
417 418 419 420 421 422
func TestRenterAsyncDownloadError(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

423
	st, _ := setupTestDownload(t, 1e4, "test.dat", false)
424
	defer st.server.panicClose()
425

Ava Howell's avatar
Ava Howell committed
426 427
	// don't wait for the upload to complete, try to download immediately to
	// intentionally cause a download error
428
	downpath := filepath.Join(st.dir, "asyncdown.dat")
429
	st.getAPI("/renter/downloadasync/test.dat?destination="+downpath, nil)
430

431
	// verify the file has an error
432
	var rdq RenterDownloadQueue
433
	err := st.getAPI("/renter/downloads", &rdq)
434 435 436 437
	if err != nil {
		t.Fatal(err)
	}
	for _, download := range rdq.Downloads {
438
		if download.SiaPath.String() == "test.dat" && download.Received == download.Filesize && download.Error == "" {
439 440 441 442 443
			t.Fatal("download had nil error")
		}
	}
}

Ava Howell's avatar
Ava Howell committed
444 445 446 447 448 449 450 451
// TestRenterAsyncDownload tests that the /renter/downloadasync route works
// correctly.
func TestRenterAsyncDownload(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()

452
	st, _ := setupTestDownload(t, 1e4, "test.dat", true)
453
	defer st.server.panicClose()
Ava Howell's avatar
Ava Howell committed
454

455
	// Download the file asynchronously.
Ava Howell's avatar
Ava Howell committed
456
	downpath := filepath.Join(st.dir, "asyncdown.dat")
457
	err := st.getAPI("/renter/downloadasync/test.dat?destination="+downpath, nil)
Ava Howell's avatar
Ava Howell committed
458 459 460 461 462
	if err != nil {
		t.Fatal(err)
	}

	// download should eventually complete
463
	var rdq RenterDownloadQueue
Ava Howell's avatar
Ava Howell committed
464 465 466 467 468 469 470
	success := false
	for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(time.Millisecond * 10) {
		err = st.getAPI("/renter/downloads", &rdq)
		if err != nil {
			t.Fatal(err)
		}
		for _, download := range rdq.Downloads {
471
			if download.Received == download.Filesize && download.SiaPath.String() == "test.dat" {
Ava Howell's avatar
Ava Howell committed
472 473 474 475 476 477 478 479 480 481 482 483
				success = true
			}
		}
		if success {
			break
		}
	}
	if !success {
		t.Fatal("/renter/downloadasync did not download our test file")
	}
}

Luke Champine's avatar
Luke Champine committed
484 485 486
// TestRenterPaths tests that the /renter routes handle path parameters
// properly.
func TestRenterPaths(t *testing.T) {
487 488 489
	if testing.Short() {
		t.SkipNow()
	}
490
	t.Parallel()
491
	st, err := createServerTester(t.Name())
Luke Champine's avatar
Luke Champine committed
492 493 494
	if err != nil {
		t.Fatal(err)
	}
495
	defer st.server.panicClose()
Luke Champine's avatar
Luke Champine committed
496 497 498 499 500 501 502 503

	// Announce the host.
	err = st.announceHost()
	if err != nil {
		t.Fatal(err)
	}

	// Create a file.
504
	path := filepath.Join(build.SiaTestingDir, "api", t.Name(), "test.dat")
505
	err = createRandFile(path, 1024)
Luke Champine's avatar
Luke Champine committed
506 507 508
	if err != nil {
		t.Fatal(err)
	}
Luke Champine's avatar
Luke Champine committed
509 510 511 512 513 514

	// Upload to host.
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
	uploadValues.Set("renew", "true")
	err = st.stdPostAPI("/renter/upload/foo/bar/test", uploadValues)
Luke Champine's avatar
Luke Champine committed
515 516 517 518
	if err != nil {
		t.Fatal(err)
	}

Luke Champine's avatar
Luke Champine committed
519 520 521 522 523 524
	// File should be listed by the renter.
	var rf RenterFiles
	err = st.getAPI("/renter/files", &rf)
	if err != nil {
		t.Fatal(err)
	}
525
	if len(rf.Files) != 1 || rf.Files[0].SiaPath.String() != "foo/bar/test" {
Luke Champine's avatar
Luke Champine committed
526 527 528 529 530 531 532 533 534
		t.Fatal("/renter/files did not return correct file:", rf)
	}
}

// TestRenterConflicts tests that the renter handles naming conflicts properly.
func TestRenterConflicts(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
535
	t.Parallel()
536
	st, err := createServerTester(t.Name())
Luke Champine's avatar
Luke Champine committed
537 538 539
	if err != nil {
		t.Fatal(err)
	}
540
	defer st.server.panicClose()
Luke Champine's avatar
Luke Champine committed
541 542 543 544 545 546 547 548

	// Announce the host.
	err = st.announceHost()
	if err != nil {
		t.Fatal(err)
	}

	// Create a file.
549
	path := filepath.Join(build.SiaTestingDir, "api", t.Name(), "test.dat")
550
	err = createRandFile(path, 1024)
Luke Champine's avatar
Luke Champine committed
551 552 553 554 555 556 557
	if err != nil {
		t.Fatal(err)
	}

	// Upload to host, using a path designed to cause conflicts. The renter
	// should automatically create a folder called foo/bar.sia. Later, we'll
	// exploit this by uploading a file called foo/bar.
Luke Champine's avatar
Luke Champine committed
558 559 560
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
	uploadValues.Set("renew", "true")
Luke Champine's avatar
Luke Champine committed
561
	err = st.stdPostAPI("/renter/upload/foo/bar.sia/test", uploadValues)
Luke Champine's avatar
Luke Champine committed
562 563 564 565
	if err != nil {
		t.Fatal(err)
	}

Luke Champine's avatar
Luke Champine committed
566 567 568 569 570 571
	// File should be listed by the renter.
	var rf RenterFiles
	err = st.getAPI("/renter/files", &rf)
	if err != nil {
		t.Fatal(err)
	}
572
	if len(rf.Files) != 1 || rf.Files[0].SiaPath.String() != "foo/bar.sia/test" {
Luke Champine's avatar
Luke Champine committed
573 574 575 576 577
		t.Fatal("/renter/files did not return correct file:", rf)
	}

	// Upload using the same nickname.
	err = st.stdPostAPI("/renter/upload/foo/bar.sia/test", uploadValues)
578
	if err == nil {
579
		t.Fatalf("expected %v, got %v", Error{"upload failed: " + siafile.ErrPathOverload.Error()}, err)
Luke Champine's avatar
Luke Champine committed
580 581 582 583 584 585
	}

	// Upload using nickname that conflicts with folder.
	err = st.stdPostAPI("/renter/upload/foo/bar", uploadValues)
	if err == nil {
		t.Fatal("expecting conflict error, got nil")
Luke Champine's avatar
Luke Champine committed
586 587
	}
}
588

589 590
// TestRenterHandlerContracts checks that contract formation between a host and
// renter behaves as expected, and that contract spending is the right amount.
591 592 593 594
func TestRenterHandlerContracts(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
595
	t.Parallel()
596
	st, err := createServerTester(t.Name())
597 598 599
	if err != nil {
		t.Fatal(err)
	}
600
	defer st.server.panicClose()
601

602
	// Announce the host and start accepting contracts.
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// The renter should not have any contracts yet.
	var contracts RenterContracts
	if err = st.getAPI("/renter/contracts", &contracts); err != nil {
		t.Fatal(err)
	}
	if len(contracts.Contracts) != 0 {
		t.Fatalf("expected renter to have 0 contracts; got %v", len(contracts.Contracts))
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
626
	allowanceValues.Set("renewwindow", testRenewWindow)
627
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
628 629 630 631
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

David Vorick's avatar
David Vorick committed
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
	// Block until the allowance has finished forming contracts.
	err = build.Retry(50, time.Millisecond*250, func() error {
		var rc RenterContracts
		err = st.getAPI("/renter/contracts", &rc)
		if err != nil {
			return errors.New("couldn't get renter stats")
		}
		if len(rc.Contracts) != 1 {
			return errors.New("no contracts")
		}
		return nil
	})
	if err != nil {
		t.Fatal("allowance setting failed")
	}

648 649 650 651 652 653 654
	// The renter should now have 1 contract.
	if err = st.getAPI("/renter/contracts", &contracts); err != nil {
		t.Fatal(err)
	}
	if len(contracts.Contracts) != 1 {
		t.Fatalf("expected renter to have 1 contract; got %v", len(contracts.Contracts))
	}
655 656 657
	if !contracts.Contracts[0].GoodForUpload || !contracts.Contracts[0].GoodForRenew {
		t.Errorf("expected contract to be good for upload and renew")
	}
658 659 660 661 662 663

	// Check the renter's contract spending.
	var get RenterGET
	if err = st.getAPI("/renter", &get); err != nil {
		t.Fatal(err)
	}
664
	expectedContractSpending := types.ZeroCurrency
665
	for _, contract := range contracts.Contracts {
666
		expectedContractSpending = expectedContractSpending.Add(contract.TotalCost)
667
	}
668
	if got := get.FinancialMetrics.TotalAllocated; got.Cmp(expectedContractSpending) != 0 {
669 670 671 672
		t.Fatalf("expected contract spending to be %v; got %v", expectedContractSpending, got)
	}
}

673 674 675
// TestRenterHandlerGetAndPost checks that valid /renter calls successfully set
// allowance values, while /renter calls with invalid allowance values are
// correctly handled.
676 677 678 679
func TestRenterHandlerGetAndPost(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
680
	t.Parallel()
681
	st, err := createServerTester(t.Name())
682 683 684
	if err != nil {
		t.Fatal(err)
	}
685
	defer st.server.panicClose()
686

687
	// Announce the host and start accepting contracts.
688 689 690 691 692 693 694 695 696 697 698 699 700 701
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
702
	allowanceValues.Set("renewwindow", testRenewWindow)
703
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

	// Check that a call to /renter returns the expected values.
	var get RenterGET
	if err = st.getAPI("/renter", &get); err != nil {
		t.Fatal(err)
	}
	// Check the renter's funds.
	expectedFunds, ok := scanAmount(testFunds)
	if !ok {
		t.Fatal("scanAmount failed")
	}
	if got := get.Settings.Allowance.Funds; got.Cmp(expectedFunds) != 0 {
		t.Fatalf("expected funds to be %v; got %v", expectedFunds, got)
	}
	// Check the renter's period.
	intPeriod, err := strconv.Atoi(testPeriod)
	if err != nil {
		t.Fatal(err)
	}
	expectedPeriod := types.BlockHeight(intPeriod)
	if got := get.Settings.Allowance.Period; got != expectedPeriod {
		t.Fatalf("expected period to be %v; got %v", expectedPeriod, got)
	}
	// Check the renter's renew window.
	expectedRenewWindow := expectedPeriod / 2
	if got := get.Settings.Allowance.RenewWindow; got != expectedRenewWindow {
		t.Fatalf("expected renew window to be %v; got %v", expectedRenewWindow, got)
	}
	// Try an invalid period string.
	allowanceValues.Set("period", "-1")
	err = st.stdPostAPI("/renter", allowanceValues)
738 739
	if err == nil || !strings.Contains(err.Error(), "unable to parse period") {
		t.Errorf("expected error to begin with 'unable to parse period'; got %v", err)
740
	}
741 742 743
	// Try to set a zero renew window
	allowanceValues.Set("period", "2")
	allowanceValues.Set("renewwindow", "0")
744 745 746 747
	err = st.stdPostAPI("/renter", allowanceValues)
	if err == nil || err.Error() != contractor.ErrAllowanceZeroWindow.Error() {
		t.Errorf("expected error to be %v, got %v", contractor.ErrAllowanceZeroWindow, err)
	}
748
	// Try to set a negative bandwidth limit
749
	allowanceValues.Set("maxdownloadspeed", "-1")
750 751
	allowanceValues.Set("renewwindow", "1")
	err = st.stdPostAPI("/renter", allowanceValues)
752
	if err == nil {
753 754
		t.Errorf("expected error to be 'download/upload rate limit...'; got %v", err)
	}
755
	allowanceValues.Set("maxuploadspeed", "-1")
756
	err = st.stdPostAPI("/renter", allowanceValues)
757
	if err == nil {
758 759
		t.Errorf("expected error to be 'download/upload rate limit...'; got %v", err)
	}
760 761
}

762 763
// TestRenterLoadNonexistent checks that attempting to upload or download a
// nonexistent file triggers the appropriate error.
764 765 766 767
func TestRenterLoadNonexistent(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
768
	t.Parallel()
769
	st, err := createServerTester(t.Name())
770 771 772
	if err != nil {
		t.Fatal(err)
	}
773
	defer st.server.panicClose()
774

775
	// Announce the host and start accepting contracts.
776 777 778 779 780 781 782 783 784 785 786 787 788 789
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
790
	allowanceValues.Set("renewwindow", testRenewWindow)
791
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
792 793 794 795 796 797 798 799 800
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

	// Try uploading a nonexistent file.
	fakepath := filepath.Join(st.dir, "dne.dat")
	uploadValues := url.Values{}
	uploadValues.Set("source", fakepath)
	err = st.stdPostAPI("/renter/upload/dne", uploadValues)
Luke Champine's avatar
Luke Champine committed
801 802
	if err == nil {
		t.Errorf("expected error when uploading nonexistent file")
803 804 805 806 807
	}

	// Try downloading a nonexistent file.
	downpath := filepath.Join(st.dir, "dnedown.dat")
	err = st.stdGetAPI("/renter/download/dne?destination=" + downpath)
808
	if err == nil {
Thomas Bennett's avatar
Thomas Bennett committed
809
		t.Error("should not be able to download non-existent file")
810 811 812 813 814 815 816 817 818 819 820 821
	}

	// The renter's downloads queue should be empty.
	var queue RenterDownloadQueue
	if err = st.getAPI("/renter/downloads", &queue); err != nil {
		t.Fatal(err)
	}
	if len(queue.Downloads) != 0 {
		t.Fatalf("expected renter to have 0 downloads in the queue; got %v", len(queue.Downloads))
	}
}

822
// TestRenterHandlerRename checks that valid /renter/rename calls are
823
// successful, and that invalid calls fail with the appropriate error.
824 825 826 827
func TestRenterHandlerRename(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
828
	t.Parallel()
829
	st, err := createServerTester(t.Name())
830 831 832
	if err != nil {
		t.Fatal(err)
	}
833
	defer st.server.panicClose()
834

835
	// Announce the host and start accepting contracts.
836 837 838 839 840 841 842 843 844 845
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

846 847 848 849
	// Try renaming a nonexistent file.
	renameValues := url.Values{}
	renameValues.Set("newsiapath", "newdne")
	err = st.stdPostAPI("/renter/rename/dne", renameValues)
Matthew Sevey's avatar
Matthew Sevey committed
850 851
	if err == nil || err.Error() != siafile.ErrUnknownPath.Error() {
		t.Errorf("Expected '%v' got '%v'", siafile.ErrUnknownPath, err)
852 853
	}

854 855 856 857
	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
858
	allowanceValues.Set("renewwindow", testRenewWindow)
859
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
860 861 862 863
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
	// Block until the allowance has finished forming contracts.
	err = build.Retry(50, time.Millisecond*250, func() error {
		var rc RenterContracts
		err = st.getAPI("/renter/contracts", &rc)
		if err != nil {
			return errors.New("couldn't get renter stats")
		}
		if len(rc.Contracts) != 1 {
			return errors.New("no contracts")
		}
		return nil
	})
	if err != nil {
		t.Fatal("allowance setting failed")
	}

880
	// Create a file.
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
	path1 := filepath.Join(st.dir, "test1.dat")
	if err = createRandFile(path1, 512); err != nil {
		t.Fatal(err)
	}

	// Upload to host.
	uploadValues := url.Values{}
	uploadValues.Set("source", path1)
	if err = st.stdPostAPI("/renter/upload/test1", uploadValues); err != nil {
		t.Fatal(err)
	}

	// Try renaming to an empty string.
	renameValues.Set("newsiapath", "")
	err = st.stdPostAPI("/renter/rename/test1", renameValues)
896 897
	if err == nil || err.Error() != modules.ErrEmptySiaPath.Error() {
		t.Fatalf("expected error to be %v; got %v", modules.ErrEmptySiaPath, err)
898 899
	}

900
	// Rename the file.
901 902 903 904 905
	renameValues.Set("newsiapath", "newtest1")
	if err = st.stdPostAPI("/renter/rename/test1", renameValues); err != nil {
		t.Fatal(err)
	}

906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
	// Should be able to continue uploading and downloading using the new name.
	var rf RenterFiles
	for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ {
		st.getAPI("/renter/files", &rf)
		time.Sleep(100 * time.Millisecond)
	}
	if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 {
		t.Fatal("upload is not succeeding:", rf.Files[0])
	}
	err = st.stdGetAPI("/renter/download/newtest1?destination=" + filepath.Join(st.dir, "testdown2.dat"))
	if err != nil {
		t.Fatal(err)
	}

	// Create and upload another file.
	path2 := filepath.Join(st.dir, "test2.dat")
	if err = createRandFile(path2, 512); err != nil {
		t.Fatal(err)
	}
	uploadValues.Set("source", path2)
	if err = st.stdPostAPI("/renter/upload/test2", uploadValues); err != nil {
		t.Fatal(err)
	}
	// Try renaming to a name that's already taken.
	renameValues.Set("newsiapath", "newtest1")
	err = st.stdPostAPI("/renter/rename/test2", renameValues)
932 933
	if err == nil || err.Error() != siafile.ErrPathOverload.Error() {
		t.Errorf("expected error to be %v; got %v", siafile.ErrPathOverload, err)
934 935 936
	}
}

937 938 939
// TestRenterHandlerDelete checks that deleting a valid file from the renter
// goes as planned and that attempting to delete a nonexistent file fails with
// the appropriate error.
940 941 942 943
func TestRenterHandlerDelete(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
944
	t.Parallel()
945
	st, err := createServerTester(t.Name())
946 947 948
	if err != nil {
		t.Fatal(err)
	}
949
	defer st.server.panicClose()
950

951
	// Announce the host and start accepting contracts.
952 953 954 955 956 957 958 959 960 961 962 963 964 965
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
966
	allowanceValues.Set("renewwindow", testRenewWindow)
967
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
968 969 970 971 972
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

	// Create a file.
973
	path := filepath.Join(st.dir, "test.dat")
974 975 976 977 978 979 980 981 982 983 984
	if err = createRandFile(path, 1024); err != nil {
		t.Fatal(err)
	}

	// Upload to host.
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
	if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil {
		t.Fatal(err)
	}

985
	// Delete the file.
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
	if err = st.stdPostAPI("/renter/delete/test", url.Values{}); err != nil {
		t.Fatal(err)
	}

	// The renter's list of files should now be empty.
	var files RenterFiles
	if err = st.getAPI("/renter/files", &files); err != nil {
		t.Fatal(err)
	}
	if len(files.Files) != 0 {
		t.Fatalf("renter's list of files should be empty; got %v instead", files)
	}

	// Try deleting a nonexistent file.
	err = st.stdPostAPI("/renter/delete/dne", url.Values{})
1001 1002
	if err == nil || err.Error() != siafile.ErrUnknownPath.Error() {
		t.Errorf("Expected '%v' got '%v'", siafile.ErrUnknownPath, err)
1003 1004
	}
}
John's avatar
John committed
1005 1006 1007 1008 1009 1010

// Tests that the /renter/upload call checks for relative paths.
func TestRenterRelativePathErrorUpload(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
1011
	t.Parallel()
1012
	st, err := createServerTester(t.Name())
John's avatar
John committed
1013 1014 1015
	if err != nil {
		t.Fatal(err)
	}
1016
	defer st.server.panicClose()
John's avatar
John committed
1017

1018
	// Announce the host and start accepting contracts.
John's avatar
John committed
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
1033
	allowanceValues.Set("renewwindow", testRenewWindow)
1034
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
John's avatar
John committed
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

	renterUploadAbsoluteError := "source must be an absolute path"

	// Create a file.
	path := filepath.Join(st.dir, "test.dat")
	if err = createRandFile(path, 1024); err != nil {
		t.Fatal(err)
	}

	// This should fail.
	uploadValues := url.Values{}
	uploadValues.Set("source", "test.dat")
	if err = st.stdPostAPI("/renter/upload/test", uploadValues); err.Error() != renterUploadAbsoluteError {
		t.Fatal(err)
	}

	// As should this.
	uploadValues = url.Values{}
	uploadValues.Set("source", "../test.dat")
	if err = st.stdPostAPI("/renter/upload/test", uploadValues); err.Error() != renterUploadAbsoluteError {
		t.Fatal(err)
	}

	// This should succeed.
	uploadValues = url.Values{}
	uploadValues.Set("source", path)
	if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil {
		t.Fatal(err)
	}
}

// Tests that the /renter/download call checks for relative paths.
func TestRenterRelativePathErrorDownload(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
1074
	t.Parallel()
1075
	st, err := createServerTester(t.Name())
John's avatar
John committed
1076 1077 1078
	if err != nil {
		t.Fatal(err)
	}
1079
	defer st.server.panicClose()
John's avatar
John committed
1080

1081
	// Announce the host and start accepting contracts.
John's avatar
John committed
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
	if err := st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.acceptContracts(); err != nil {
		t.Fatal(err)
	}
	if err = st.setHostStorage(); err != nil {
		t.Fatal(err)
	}

	// Set an allowance for the renter, allowing a contract to be formed.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", testFunds)
	allowanceValues.Set("period", testPeriod)
1096
	allowanceValues.Set("renewwindow", testRenewWindow)
1097
	allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts))
John's avatar
John committed
1098 1099 1100 1101
	if err = st.stdPostAPI("/renter", allowanceValues); err != nil {
		t.Fatal(err)
	}

1102
	renterDownloadAbsoluteError := "download failed: destination must be an absolute path"
John's avatar
John committed
1103

1104
	// Create a file, and upload it.
John's avatar
John committed
1105 1106 1107 1108 1109 1110 1111 1112 1113
	path := filepath.Join(st.dir, "test.dat")
	if err = createRandFile(path, 1024); err != nil {
		t.Fatal(err)
	}
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
	if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil {
		t.Fatal(err)
	}
1114
	var rf RenterFiles
1115
	for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ {
1116 1117 1118 1119
		st.getAPI("/renter/files", &rf)
		time.Sleep(200 * time.Millisecond)
	}
	if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 {
Ava Howell's avatar
Ava Howell committed
1120
		t.Fatal("the uploading is not succeeding for some reason:", rf.Files[0])
1121
	}
John's avatar
John committed
1122

1123
	// Use a relative destination, which should fail.
John's avatar
John committed
1124 1125 1126 1127 1128
	downloadPath := "test1.dat"
	if err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath); err.Error() != renterDownloadAbsoluteError {
		t.Fatal(err)
	}

1129
	// Relative destination stepping backwards should also fail.
John's avatar
John committed
1130 1131 1132 1133 1134
	downloadPath = "../test1.dat"
	if err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath); err.Error() != renterDownloadAbsoluteError {
		t.Fatal(err)
	}

1135 1136
	// Long relative destination should also fail (just missing leading slash).
	downloadPath = filepath.Join(st.dir[1:], "test1.dat")
1137 1138
	err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath)
	if err == nil {
1139 1140 1141 1142 1143 1144 1145
		t.Fatal("expecting an error")
	}

	// Full destination should succeed.
	downloadPath = filepath.Join(st.dir, "test1.dat")
	err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath)
	if err != nil {
1146 1147
		t.Fatal("expecting an error")
	}
John's avatar
John committed
1148
}
1149 1150 1151 1152 1153 1154 1155 1156

// TestRenterPricesHandler checks that the prices command returns reasonable
// values given the settings of the hosts.
func TestRenterPricesHandler(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
1157
	st, err := createServerTester(t.Name())
1158 1159 1160
	if err != nil {
		t.Fatal(err)
	}
1161
	defer st.server.panicClose()
1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173

	// Announce the host and then get the calculated prices for when there is a
	// single host.
	var rpeSingle modules.RenterPriceEstimation
	if err = st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.getAPI("/renter/prices", &rpeSingle); err != nil {
		t.Fatal(err)
	}

	// Create several more hosts all using the default settings.
1174
	stHost1, err := blankServerTester(t.Name() + " - Host 1")
1175 1176 1177
	if err != nil {
		t.Fatal(err)
	}
1178
	defer stHost1.panicClose()
1179
	stHost2, err := blankServerTester(t.Name() + " - Host 2")
1180 1181 1182
	if err != nil {
		t.Fatal(err)
	}
1183
	defer stHost2.panicClose()
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212

	// Connect all the nodes and announce all of the hosts.
	sts := []*serverTester{st, stHost1, stHost2}
	err = fullyConnectNodes(sts)
	if err != nil {
		t.Fatal(err)
	}
	err = fundAllNodes(sts)
	if err != nil {
		t.Fatal(err)
	}
	err = announceAllHosts(sts)
	if err != nil {
		t.Fatal(err)
	}

	// Grab the price estimates for when there are a bunch of hosts with the
	// same stats.
	var rpeMulti modules.RenterPriceEstimation
	if err = st.getAPI("/renter/prices", &rpeMulti); err != nil {
		t.Fatal(err)
	}

	// Verify that the aggregate is the same.
	if !rpeMulti.DownloadTerabyte.Equals(rpeSingle.DownloadTerabyte) {
		t.Log(rpeMulti.DownloadTerabyte)
		t.Log(rpeSingle.DownloadTerabyte)
		t.Error("price changed from single to multi")
	}
1213 1214
	if rpeMulti.FormContracts.Equals(rpeSingle.FormContracts) {
		t.Error("price of forming contracts should have increased from single to multi")
1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
	}
	if !rpeMulti.StorageTerabyteMonth.Equals(rpeSingle.StorageTerabyteMonth) {
		t.Error("price changed from single to multi")
	}
	if !rpeMulti.UploadTerabyte.Equals(rpeSingle.UploadTerabyte) {
		t.Error("price changed from single to multi")
	}
}

// TestRenterPricesHandlerPricey checks that the prices command returns
// reasonable values given the settings of the hosts.
func TestRenterPricesHandlerPricey(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
1231
	st, err := createServerTester(t.Name())
1232 1233 1234
	if err != nil {
		t.Fatal(err)
	}
1235
	defer st.server.panicClose()
1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247

	// Announce the host and then get the calculated prices for when there is a
	// single host.
	var rpeSingle modules.RenterPriceEstimation
	if err = st.announceHost(); err != nil {
		t.Fatal(err)
	}
	if err = st.getAPI("/renter/prices", &rpeSingle); err != nil {
		t.Fatal(err)
	}

	// Create several more hosts all using the default settings.
1248
	stHost1, err := blankServerTester(t.Name() + " - Host 1")
1249 1250 1251
	if err != nil {
		t.Fatal(err)
	}
1252
	stHost2, err := blankServerTester(t.Name() + " - Host 2")
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270
	if err != nil {
		t.Fatal(err)
	}

	var hg HostGET
	err = st.getAPI("/host", &hg)
	if err != nil {
		t.Fatal(err)
	}
	err = stHost1.getAPI("/host", &hg)
	if err != nil {
		t.Fatal(err)
	}
	err = stHost2.getAPI("/host", &hg)
	if err != nil {
		t.Fatal(err)
	}

1271 1272
	// Set host 2 to be more expensive than the rest by a substantial amount. This
	// should result in an increase for the price estimation.
1273 1274
	vals := url.Values{}
	vals.Set("mindownloadbandwidthprice", "100000000000000000000")
1275 1276
	vals.Set("mincontractprice", "1000000000000000000000000")
	vals.Set("minstorageprice", "1000000000000000000000")
1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
	vals.Set("minuploadbandwidthprice", "100000000000000000000")
	err = stHost2.stdPostAPI("/host", vals)
	if err != nil {
		t.Fatal(err)
	}

	// Connect all the nodes and announce all of the hosts.
	sts := []*serverTester{st, stHost1, stHost2}
	err = fullyConnectNodes(sts)
	if err != nil {
		t.Fatal(err)
	}
	err = fundAllNodes(sts)
	if err != nil {
		t.Fatal(err)
	}
	err = announceAllHosts(sts)
	if err != nil {
		t.Fatal(err)
	}

	// Grab the price estimates for when there are a bunch of hosts with the
	// same stats.
	var rpeMulti modules.RenterPriceEstimation
	if err = st.getAPI("/renter/prices", &rpeMulti); err != nil {
		t.Fatal(err)
	}

1305 1306 1307 1308 1309
	// Verify that the estimate for downloading, uploading, and storing
	// increased but the estimate for formaing contracts decreased. Forming
	// contracts decreases because with a more expensive host you are not able
	// to store as much data therefore reducing the amount of host collateral
	// you have to pay the siafund fee on
1310
	if !(rpeMulti.DownloadTerabyte.Cmp(rpeSingle.DownloadTerabyte) > 0) {
1311 1312
		t.Log("Multi DownloadTerabyte cost:", rpeMulti.DownloadTerabyte.HumanString())
		t.Log("Single DownloadTerabyte cost:", rpeSingle.DownloadTerabyte.HumanString())
1313
		t.Error("price did not increase from single to multi")
1314
	}
1315 1316 1317
	if rpeMulti.FormContracts.Cmp(rpeSingle.FormContracts) > 0 {
		t.Log("Multi FormContracts cost:", rpeMulti.FormContracts.HumanString())
		t.Log("Single FormContracts cost:", rpeSingle.FormContracts.HumanString())
1318 1319 1320
		t.Error("price did not drop from single to multi")
	}
	if !(rpeMulti.StorageTerabyteMonth.Cmp(rpeSingle.StorageTerabyteMonth) > 0) {
1321 1322
		t.Log("Multi StorageTerabyteMonth cost:", rpeMulti.StorageTerabyteMonth.HumanString())
		t.Log("Single StorageTerabyteMonth cost:", rpeSingle.StorageTerabyteMonth.HumanString())
1323
		t.Error("price did not increase from single to multi")
1324 1325
	}
	if !(rpeMulti.UploadTerabyte.Cmp(rpeSingle.UploadTerabyte) > 0) {
1326 1327
		t.Log("Multi UploadTerabyte cost:", rpeMulti.UploadTerabyte.HumanString())
		t.Log("Single UploadTerabyte cost:", rpeSingle.UploadTerabyte.HumanString())
1328
		t.Error("price did not increase from single to multi")
1329 1330
	}
}
1331 1332 1333 1334 1335

// TestContractorHostRemoval checks that the contractor properly migrates away
// from low quality hosts when there are higher quality hosts available.
func TestContractorHostRemoval(t *testing.T) {
	// Create a renter and 2 hosts. Connect to the hosts and start uploading.
1336
	if testing.Short() || !build.VLONG {
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
		t.SkipNow()
	}
	st, err := createServerTester(t.Name() + "renter")
	if err != nil {
		t.Fatal(err)
	}
	defer st.server.panicClose()
	stH1, err := blankServerTester(t.Name() + " - Host 1")
	if err != nil {
		t.Fatal(err)
	}
	defer stH1.server.Close()
	testGroup := []*serverTester{st, stH1}

	// Connect the testers to eachother so that they are all on the same
	// blockchain.
	err = fullyConnectNodes(testGroup)
	if err != nil {
		t.Fatal(err)
	}
	// Make sure that every wallet has money in it.
	err = fundAllNodes(testGroup)
	if err != nil {
		t.Fatal(err)
	}

	// Add storage to every host.
	err = addStorageToAllHosts(testGroup)
	if err != nil {
		t.Fatal(err)
	}
	// Raise the prices significantly for the two hosts.
	raisedPrice := url.Values{}
	raisedPrice.Set("mincontractprice", "5000000000000000000000000000") // 5 KS
	raisedPrice.Set("period", testPeriod)
	err = st.stdPostAPI("/host", raisedPrice)
	if err != nil {
		t.Fatal(err)
	}
	err = stH1.stdPostAPI("/host", raisedPrice)
	if err != nil {
		t.Fatal(err)
	}
1380
	// Announce the hosts.
1381 1382 1383 1384 1385 1386 1387 1388 1389
	err = announceAllHosts(testGroup)
	if err != nil {
		t.Fatal(err)
	}

	// Set an allowance with two hosts.
	allowanceValues := url.Values{}
	allowanceValues.Set("funds", "500000000000000000000000000000") // 500k SC
	allowanceValues.Set("hosts", "2")
1390
	allowanceValues.Set("period", "15")
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402
	err = st.stdPostAPI("/renter", allowanceValues)
	if err != nil {
		t.Fatal(err)
	}

	// Create a file to upload.
	filesize := int(100)
	path := filepath.Join(st.dir, "test.dat")
	err = createRandFile(path, filesize)
	if err != nil {
		t.Fatal(err)
	}
1403 1404 1405 1406
	origBytes, err := ioutil.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
1407 1408 1409 1410

	// upload the file
	uploadValues := url.Values{}
	uploadValues.Set("source", path)
1411
	uploadValues.Set("datapieces", "1")
1412
	uploadValues.Set("paritypieces", "1")
1413 1414 1415 1416 1417 1418 1419
	err = st.stdPostAPI("/renter/upload/test", uploadValues)
	if err != nil {
		t.Fatal(err)
	}

	// redundancy should reach 2
	var rf RenterFiles
1420
	err = build.Retry(120, 250*time.Millisecond, func() error {
1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436
		st.getAPI("/renter/files", &rf)
		if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 {
			return nil
		}
		return errors.New("file not uploaded")
	})
	if err != nil {
		t.Fatal(err)
	}

	// verify we can download
	downloadPath := filepath.Join(st.dir, "test-downloaded-verify.dat")
	err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath)
	if err != nil {
		t.Fatal(err)
	}
1437 1438 1439 1440 1441 1442 1443
	downloadBytes, err := ioutil.ReadFile(downloadPath)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(downloadBytes, origBytes) {
		t.Fatal("downloaded file and uploaded file do not match")
	}
1444 1445 1446 1447 1448 1449 1450

	// Get the values of the first and second contract.
	var rc RenterContracts
	err = st.getAPI("/renter/contracts", &rc)
	if err != nil {
		t.Fatal(err)
	}
1451
	if len(rc.ActiveContracts) != 2 {
1452 1453
		t.Fatal("wrong contract count")
	}
1454 1455
	rc1Host := rc.ActiveContracts[0].HostPublicKey.String()
	rc2Host := rc.ActiveContracts[1].HostPublicKey.String()
1456 1457 1458 1459 1460