internal/client: fix bug in latestFixedVersion

Fix bug in latestFixedVersion and add tests. This bug only affected the
in memory client, which was used for testing.

Change-Id: I56fd4a6ad90de9cb78e3eba4d3f87fdad79e7eda
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/486806
Run-TryBot: Tatiana Bradley <tatianabradley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/internal/client/source.go b/internal/client/source.go
index 3df6a1b..e634c74 100644
--- a/internal/client/source.go
+++ b/internal/client/source.go
@@ -185,6 +185,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" && isem.Less(latestFixed, introduced) {
+					latestFixed = ""
+					break
+				}
+			}
 		}
 	}
 	return latestFixed
diff --git a/internal/client/source_test.go b/internal/client/source_test.go
index 37b324b..3acd8b3 100644
--- a/internal/client/source_test.go
+++ b/internal/client/source_test.go
@@ -9,6 +9,8 @@
 	"net/url"
 	"os"
 	"testing"
+
+	"golang.org/x/vuln/internal/osv"
 )
 
 func TestGet(t *testing.T) {
@@ -79,3 +81,132 @@
 		test(t, ms)
 	})
 }
+
+func TestLatestFixedVersion(t *testing.T) {
+	tests := []struct {
+		name   string
+		ranges []osv.Range
+		want   string
+	}{
+		{
+			name:   "empty",
+			ranges: []osv.Range{},
+			want:   "",
+		},
+		{
+			name: "no fix",
+			ranges: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.RangeEvent{
+					{
+						Introduced: "0",
+					},
+				},
+			}},
+			want: "",
+		},
+		{
+			name: "no latest fix",
+			ranges: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.RangeEvent{
+					{Introduced: "0"},
+					{Fixed: "1.0.4"},
+					{Introduced: "1.1.2"},
+				},
+			}},
+			want: "",
+		},
+		{
+			name: "unsorted no latest fix",
+			ranges: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.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: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.RangeEvent{
+					{
+						Fixed: "1.0.0",
+					},
+					{
+						Introduced: "0",
+					},
+					{
+						Fixed: "0.1.0",
+					},
+					{
+						Introduced: "0.5.0",
+					},
+				},
+			}},
+			want: "1.0.0",
+		},
+		{
+			name: "multiple ranges",
+			ranges: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.RangeEvent{
+					{
+						Introduced: "0",
+					},
+					{
+						Fixed: "0.1.0",
+					},
+				},
+			},
+				{
+					Type: osv.RangeTypeSemver,
+					Events: []osv.RangeEvent{
+						{
+							Introduced: "0",
+						},
+						{
+							Fixed: "0.2.0",
+						},
+					},
+				}},
+			want: "0.2.0",
+		},
+		{
+			name: "pseudoversion",
+			ranges: []osv.Range{{
+				Type: osv.RangeTypeSemver,
+				Events: []osv.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)
+			}
+		})
+	}
+}