internal/osv: fix bug in LatestFixedVersion

Fix bug and add tests. Only affects testing infrastructure, so impact
is minimal.

Change-Id: I93e9a88ce70412325a53efa589cd1662efaec68e
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/488775
Run-TryBot: Tatiana Bradley <tatianabradley@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/internal/osv/semver.go b/internal/osv/semver.go
index 47f4d8a..64a0083 100644
--- a/internal/osv/semver.go
+++ b/internal/osv/semver.go
@@ -111,6 +111,15 @@
 					latestFixed = fixed
 				}
 			}
+			// If the vulnerability was re-introduced after the latest fix
+			// we found, there is no latest fix for this range.
+			for _, e := range r.Events {
+				introduced := e.Introduced
+				if introduced != "" && introduced != "0" && LessSemver(latestFixed, introduced) {
+					latestFixed = ""
+					break
+				}
+			}
 		}
 	}
 	return latestFixed
diff --git a/internal/osv/semver_test.go b/internal/osv/semver_test.go
index 6c1043f..b757552 100644
--- a/internal/osv/semver_test.go
+++ b/internal/osv/semver_test.go
@@ -148,3 +148,132 @@
 		}
 	}
 }
+
+func TestLatestFixedVersion(t *testing.T) {
+	tests := []struct {
+		name   string
+		ranges []Range
+		want   string
+	}{
+		{
+			name:   "empty",
+			ranges: []Range{},
+			want:   "",
+		},
+		{
+			name: "no fix",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{
+						Introduced: "0",
+					},
+				},
+			}},
+			want: "",
+		},
+		{
+			name: "no latest fix",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{Introduced: "0"},
+					{Fixed: "1.0.4"},
+					{Introduced: "1.1.2"},
+				},
+			}},
+			want: "",
+		},
+		{
+			name: "unsorted no latest fix",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{Fixed: "1.0.4"},
+					{Introduced: "0"},
+					{Introduced: "1.1.2"},
+					{Introduced: "1.5.0"},
+					{Fixed: "1.1.4"},
+				},
+			}},
+			want: "",
+		},
+		{
+			name: "unsorted with fix",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{
+						Fixed: "1.0.0",
+					},
+					{
+						Introduced: "0",
+					},
+					{
+						Fixed: "0.1.0",
+					},
+					{
+						Introduced: "0.5.0",
+					},
+				},
+			}},
+			want: "1.0.0",
+		},
+		{
+			name: "multiple ranges",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{
+						Introduced: "0",
+					},
+					{
+						Fixed: "0.1.0",
+					},
+				},
+			},
+				{
+					Type: RangeTypeSemver,
+					Events: []RangeEvent{
+						{
+							Introduced: "0",
+						},
+						{
+							Fixed: "0.2.0",
+						},
+					},
+				}},
+			want: "0.2.0",
+		},
+		{
+			name: "pseudoversion",
+			ranges: []Range{{
+				Type: RangeTypeSemver,
+				Events: []RangeEvent{
+					{
+						Introduced: "0",
+					},
+					{
+						Fixed: "0.0.0-20220824120805-abc",
+					},
+					{
+						Introduced: "0.0.0-20230824120805-efg",
+					},
+					{
+						Fixed: "0.0.0-20240824120805-hij",
+					},
+				},
+			}},
+			want: "0.0.0-20240824120805-hij",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			got := LatestFixedVersion(test.ranges)
+			if got != test.want {
+				t.Errorf("LatestFixedVersion = %q, want %q", got, test.want)
+			}
+		})
+	}
+}