Allow users to re-import the match list in the scouting app

We currently append the match list to the existing data in the table.
This patch makes it so the entries are replaced instead of duplicated.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I86fdc49f8f01861bd0b35da8042a28e03ee9c0f0
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 99301f2..32646f1 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -204,7 +204,10 @@
 		"R1, R2, R3, B1, B2, B3) " +
 		"VALUES (" +
 		"$1, $2, $3, " +
-		"$4, $5, $6, $7, $8, $9)")
+		"$4, $5, $6, $7, $8, $9) " +
+		"ON CONFLICT (MatchNumber, Round, CompLevel) DO UPDATE SET " +
+		"R1 = EXCLUDED.R1, R2 = EXCLUDED.R2, R3 = EXCLUDED.R3, " +
+		"B1 = EXCLUDED.B1, B2 = EXCLUDED.B2, B3 = EXCLUDED.B3")
 	if err != nil {
 		return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
 	}
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 09b6eb7..290f451 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -397,6 +397,49 @@
 	}
 }
 
+func TestOverwriteNewMatchData(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	testDatabase := []Match{
+		Match{
+			MatchNumber: 1, Round: 1, CompLevel: "quals",
+			R1: 251, R2: 169, R3: 286, B1: 253, B2: 538, B3: 149,
+		},
+		Match{
+			MatchNumber: 2, Round: 1, CompLevel: "quals",
+			R1: 198, R2: 135, R3: 777, B1: 999, B2: 434, B3: 698,
+		},
+		Match{
+			MatchNumber: 1, Round: 1, CompLevel: "quals",
+			R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
+		},
+	}
+
+	for i := 0; i < len(testDatabase); i++ {
+		err := fixture.db.AddToMatch(testDatabase[i])
+		check(t, err, fmt.Sprint("Failed to add match", i))
+	}
+
+	correct := []Match{
+		Match{
+			MatchNumber: 2, Round: 1, CompLevel: "quals",
+			R1: 198, R2: 135, R3: 777, B1: 999, B2: 434, B3: 698,
+		},
+		Match{
+			MatchNumber: 1, Round: 1, CompLevel: "quals",
+			R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
+		},
+	}
+
+	got, err := fixture.db.ReturnMatches()
+	check(t, err, "Failed to get match list")
+
+	if !reflect.DeepEqual(correct, got) {
+		t.Fatalf("Got %#v,\nbut expected %#v.", got, correct)
+	}
+}
+
 func TestReturnRankingsDB(t *testing.T) {
 	fixture := createDatabase(t)
 	defer fixture.TearDown()
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index bd1b13d..c4376df 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -136,5 +136,21 @@
         self.assertEqual(stdout.count("MatchNumber:"), 12)
         self.assertEqual(len(re.findall(r": \(int32\) 4856[,\n]", stdout)), 12)
 
+    def test_request_all_matches(self):
+        """Makes sure that we can import the match list multiple times without problems."""
+        request_all_matches_outputs = []
+        for _ in range(2):
+            self.refresh_match_list()
+
+            # RequestAllMatches has no fields.
+            json_path = write_json_request({})
+            exit_code, stdout, stderr = run_debug_cli(["-requestAllMatches", json_path])
+
+            self.assertEqual(exit_code, 0, stderr)
+            request_all_matches_outputs.append(stdout)
+
+        self.maxDiff = None
+        self.assertEqual(request_all_matches_outputs[0], request_all_matches_outputs[1])
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/scouting/webserver/requests/debug/cli/main.go b/scouting/webserver/requests/debug/cli/main.go
index f6fb38a..d338b5f 100644
--- a/scouting/webserver/requests/debug/cli/main.go
+++ b/scouting/webserver/requests/debug/cli/main.go
@@ -97,6 +97,9 @@
 
 	spew.Config.Indent = *indentPtr
 
+	// Disable pointer addresses. They're not useful for our purposes.
+	spew.Config.DisablePointerAddresses = true
+
 	// Handle the actual arguments.
 	maybePerformRequest(
 		"SubmitDataScouting",