|
5 | 5 | "errors"
|
6 | 6 | "math/rand"
|
7 | 7 | "net/http"
|
| 8 | + "net/http/httptest" |
8 | 9 | "net/url"
|
9 | 10 | "sync/atomic"
|
10 | 11 | "testing"
|
@@ -397,6 +398,7 @@ func TestIncludesDetailsOnReconnect(t *testing.T) {
|
397 | 398 | eventually(t, func() bool { return atomic.LoadInt64(&receivedDetails) == 1 })
|
398 | 399 |
|
399 | 400 | // close the Agent connection. expect it to reconnect and send details again.
|
| 401 | + require.NotNil(t, client.conn) |
400 | 402 | err := client.conn.Close()
|
401 | 403 | assert.NoError(t, err)
|
402 | 404 |
|
@@ -444,7 +446,7 @@ func TestSetEffectiveConfig(t *testing.T) {
|
444 | 446 | settings.OpAMPServerURL = "ws://" + srv.Endpoint
|
445 | 447 | prepareClient(t, &settings, client)
|
446 | 448 |
|
447 |
| - assert.NoError(t, client.Start(context.Background(), settings)) |
| 449 | + require.NoError(t, client.Start(context.Background(), settings)) |
448 | 450 |
|
449 | 451 | // Verify config is delivered.
|
450 | 452 | eventually(
|
@@ -972,3 +974,253 @@ func TestRemoteConfigUpdate(t *testing.T) {
|
972 | 974 | })
|
973 | 975 | }
|
974 | 976 | }
|
| 977 | + |
| 978 | +type packageTestCase struct { |
| 979 | + name string |
| 980 | + errorOnCallback bool |
| 981 | + available *protobufs.PackagesAvailable |
| 982 | + expectedStatus *protobufs.PackageStatuses |
| 983 | + expectedFileContent map[string][]byte |
| 984 | +} |
| 985 | + |
| 986 | +const packageUpdateErrorMsg = "cannot update packages" |
| 987 | + |
| 988 | +func verifyUpdatePackages(t *testing.T, testCase packageTestCase) { |
| 989 | + testClients(t, func(t *testing.T, client OpAMPClient) { |
| 990 | + |
| 991 | + // Start a Server. |
| 992 | + srv := internal.StartMockServer(t) |
| 993 | + srv.EnableExpectMode() |
| 994 | + |
| 995 | + localPackageState := internal.NewInMemPackagesStore() |
| 996 | + |
| 997 | + var syncerDoneCh <-chan struct{} |
| 998 | + |
| 999 | + // Prepare a callback that returns either success or failure. |
| 1000 | + onPackagesAvailable := func(ctx context.Context, packages *protobufs.PackagesAvailable, syncer types.PackagesSyncer) error { |
| 1001 | + if testCase.errorOnCallback { |
| 1002 | + return errors.New(packageUpdateErrorMsg) |
| 1003 | + } else { |
| 1004 | + syncerDoneCh = syncer.Done() |
| 1005 | + err := syncer.Sync(ctx) |
| 1006 | + require.NoError(t, err) |
| 1007 | + return nil |
| 1008 | + } |
| 1009 | + } |
| 1010 | + |
| 1011 | + // Start a client. |
| 1012 | + settings := types.StartSettings{ |
| 1013 | + OpAMPServerURL: "ws://" + srv.Endpoint, |
| 1014 | + Callbacks: types.CallbacksStruct{ |
| 1015 | + OnPackagesAvailableFunc: onPackagesAvailable, |
| 1016 | + }, |
| 1017 | + PackagesStateProvider: localPackageState, |
| 1018 | + } |
| 1019 | + prepareClient(t, &settings, client) |
| 1020 | + |
| 1021 | + // Client ---> |
| 1022 | + assert.NoError(t, client.Start(context.Background(), settings)) |
| 1023 | + |
| 1024 | + // ---> Server |
| 1025 | + srv.Expect(func(msg *protobufs.AgentToServer) *protobufs.ServerToAgent { |
| 1026 | + // Send the packages to the Agent. |
| 1027 | + return &protobufs.ServerToAgent{ |
| 1028 | + InstanceUid: msg.InstanceUid, |
| 1029 | + PackagesAvailable: testCase.available, |
| 1030 | + } |
| 1031 | + }) |
| 1032 | + |
| 1033 | + // The Agent will try to install the packages and will send the status |
| 1034 | + // report about it back to the Server. |
| 1035 | + |
| 1036 | + var lastStatusHash []byte |
| 1037 | + |
| 1038 | + // ---> Server |
| 1039 | + // Wait for the expected package statuses to be received. |
| 1040 | + srv.EventuallyExpect("full PackageStatuses", |
| 1041 | + func(msg *protobufs.AgentToServer) (*protobufs.ServerToAgent, bool) { |
| 1042 | + expectedStatusReceived := false |
| 1043 | + |
| 1044 | + status := msg.PackageStatuses |
| 1045 | + require.NotNil(t, status) |
| 1046 | + assert.EqualValues(t, testCase.expectedStatus.ServerProvidedAllPackagesHash, status.ServerProvidedAllPackagesHash) |
| 1047 | + lastStatusHash = status.Hash |
| 1048 | + |
| 1049 | + // Verify individual package statuses. |
| 1050 | + for name, pkgExpected := range testCase.expectedStatus.Packages { |
| 1051 | + pkgStatus := status.Packages[name] |
| 1052 | + if pkgStatus == nil { |
| 1053 | + // Package status not yet included in the report. |
| 1054 | + continue |
| 1055 | + } |
| 1056 | + switch pkgStatus.Status { |
| 1057 | + case protobufs.PackageStatus_InstallFailed: |
| 1058 | + assert.Contains(t, pkgStatus.ErrorMessage, pkgExpected.ErrorMessage) |
| 1059 | + |
| 1060 | + case protobufs.PackageStatus_Installed: |
| 1061 | + assert.EqualValues(t, pkgExpected.AgentHasHash, pkgStatus.AgentHasHash) |
| 1062 | + assert.EqualValues(t, pkgExpected.AgentHasVersion, pkgStatus.AgentHasVersion) |
| 1063 | + assert.Empty(t, pkgStatus.ErrorMessage) |
| 1064 | + default: |
| 1065 | + assert.Empty(t, pkgStatus.ErrorMessage) |
| 1066 | + } |
| 1067 | + assert.EqualValues(t, pkgExpected.ServerOfferedHash, pkgStatus.ServerOfferedHash) |
| 1068 | + assert.EqualValues(t, pkgExpected.ServerOfferedVersion, pkgStatus.ServerOfferedVersion) |
| 1069 | + |
| 1070 | + if pkgStatus.Status == pkgExpected.Status { |
| 1071 | + expectedStatusReceived = true |
| 1072 | + assert.Len(t, status.Packages, len(testCase.available.Packages)) |
| 1073 | + } |
| 1074 | + } |
| 1075 | + assert.NotNil(t, status.Hash) |
| 1076 | + |
| 1077 | + return &protobufs.ServerToAgent{InstanceUid: msg.InstanceUid}, expectedStatusReceived |
| 1078 | + }) |
| 1079 | + |
| 1080 | + if syncerDoneCh != nil { |
| 1081 | + // Wait until all syncing is done. |
| 1082 | + <-syncerDoneCh |
| 1083 | + |
| 1084 | + for pkgName, receivedContent := range localPackageState.GetContent() { |
| 1085 | + expectedContent := testCase.expectedFileContent[pkgName] |
| 1086 | + assert.EqualValues(t, expectedContent, receivedContent) |
| 1087 | + } |
| 1088 | + } |
| 1089 | + |
| 1090 | + // Client ---> |
| 1091 | + // Trigger another status report by setting AgentDescription. |
| 1092 | + _ = client.SetAgentDescription(client.AgentDescription()) |
| 1093 | + |
| 1094 | + // ---> Server |
| 1095 | + srv.EventuallyExpect("compressed PackageStatuses", |
| 1096 | + func(msg *protobufs.AgentToServer) (*protobufs.ServerToAgent, bool) { |
| 1097 | + // Ensure that compressed status is received. |
| 1098 | + status := msg.PackageStatuses |
| 1099 | + require.NotNil(t, status) |
| 1100 | + compressedReceived := status.ServerProvidedAllPackagesHash == nil |
| 1101 | + if compressedReceived { |
| 1102 | + assert.Nil(t, status.ServerProvidedAllPackagesHash) |
| 1103 | + assert.Nil(t, status.Packages) |
| 1104 | + } |
| 1105 | + assert.NotNil(t, status.Hash) |
| 1106 | + assert.Equal(t, lastStatusHash, status.Hash) |
| 1107 | + |
| 1108 | + response := &protobufs.ServerToAgent{InstanceUid: msg.InstanceUid} |
| 1109 | + |
| 1110 | + if compressedReceived { |
| 1111 | + // Ask for full report again. |
| 1112 | + response.Flags = protobufs.ServerToAgent_ReportPackageStatuses |
| 1113 | + } else { |
| 1114 | + // Keep triggering status report by setting AgentDescription |
| 1115 | + // until the compressed PackageStatuses arrives. |
| 1116 | + _ = client.SetAgentDescription(client.AgentDescription()) |
| 1117 | + } |
| 1118 | + |
| 1119 | + return response, compressedReceived |
| 1120 | + }) |
| 1121 | + |
| 1122 | + // Shutdown the Server. |
| 1123 | + srv.Close() |
| 1124 | + |
| 1125 | + // Shutdown the client. |
| 1126 | + err := client.Stop(context.Background()) |
| 1127 | + assert.NoError(t, err) |
| 1128 | + }) |
| 1129 | +} |
| 1130 | + |
| 1131 | +// Downloadable package file constants. |
| 1132 | +const packageFileURL = "/validfile.pkg" |
| 1133 | + |
| 1134 | +var packageFileContent = []byte("Package File Content") |
| 1135 | + |
| 1136 | +func createDownloadSrv(t *testing.T) *httptest.Server { |
| 1137 | + m := http.NewServeMux() |
| 1138 | + m.HandleFunc(packageFileURL, |
| 1139 | + func(w http.ResponseWriter, r *http.Request) { |
| 1140 | + w.WriteHeader(http.StatusOK) |
| 1141 | + _, err := w.Write(packageFileContent) |
| 1142 | + assert.NoError(t, err) |
| 1143 | + }, |
| 1144 | + ) |
| 1145 | + |
| 1146 | + srv := httptest.NewServer(m) |
| 1147 | + |
| 1148 | + u, err := url.Parse(srv.URL) |
| 1149 | + if err != nil { |
| 1150 | + t.Fatal(err) |
| 1151 | + } |
| 1152 | + endpoint := u.Host |
| 1153 | + testhelpers.WaitForEndpoint(endpoint) |
| 1154 | + |
| 1155 | + return srv |
| 1156 | +} |
| 1157 | + |
| 1158 | +func createPackageTestCase(name string, downloadSrv *httptest.Server) packageTestCase { |
| 1159 | + return packageTestCase{ |
| 1160 | + name: name, |
| 1161 | + errorOnCallback: false, |
| 1162 | + available: &protobufs.PackagesAvailable{ |
| 1163 | + Packages: map[string]*protobufs.PackageAvailable{ |
| 1164 | + "package1": { |
| 1165 | + Type: protobufs.PackageAvailable_TopLevelPackage, |
| 1166 | + Version: "1.0.0", |
| 1167 | + File: &protobufs.DownloadableFile{ |
| 1168 | + DownloadUrl: downloadSrv.URL + packageFileURL, |
| 1169 | + ContentHash: []byte{4, 5}, |
| 1170 | + }, |
| 1171 | + Hash: []byte{1, 2, 3}, |
| 1172 | + }, |
| 1173 | + }, |
| 1174 | + AllPackagesHash: []byte{1, 2, 3, 4, 5}, |
| 1175 | + }, |
| 1176 | + |
| 1177 | + expectedStatus: &protobufs.PackageStatuses{ |
| 1178 | + Packages: map[string]*protobufs.PackageStatus{ |
| 1179 | + "package1": { |
| 1180 | + Name: "package1", |
| 1181 | + AgentHasVersion: "1.0.0", |
| 1182 | + AgentHasHash: []byte{1, 2, 3}, |
| 1183 | + ServerOfferedVersion: "1.0.0", |
| 1184 | + ServerOfferedHash: []byte{1, 2, 3}, |
| 1185 | + Status: protobufs.PackageStatus_Installed, |
| 1186 | + ErrorMessage: "", |
| 1187 | + }, |
| 1188 | + }, |
| 1189 | + ServerProvidedAllPackagesHash: []byte{1, 2, 3, 4, 5}, |
| 1190 | + }, |
| 1191 | + |
| 1192 | + expectedFileContent: map[string][]byte{ |
| 1193 | + "package1": packageFileContent, |
| 1194 | + }, |
| 1195 | + } |
| 1196 | +} |
| 1197 | + |
| 1198 | +func TestUpdatePackages(t *testing.T) { |
| 1199 | + |
| 1200 | + downloadSrv := createDownloadSrv(t) |
| 1201 | + defer downloadSrv.Close() |
| 1202 | + |
| 1203 | + // A success case. |
| 1204 | + var tests []packageTestCase |
| 1205 | + tests = append(tests, createPackageTestCase("success", downloadSrv)) |
| 1206 | + |
| 1207 | + // A case when downloading the file fails because the URL is incorrect. |
| 1208 | + notFound := createPackageTestCase("downloadable file not found", downloadSrv) |
| 1209 | + notFound.available.Packages["package1"].File.DownloadUrl = downloadSrv.URL + "/notfound" |
| 1210 | + notFound.expectedStatus.Packages["package1"].Status = protobufs.PackageStatus_InstallFailed |
| 1211 | + notFound.expectedStatus.Packages["package1"].ErrorMessage = "cannot download" |
| 1212 | + tests = append(tests, notFound) |
| 1213 | + |
| 1214 | + // A case when OnPackagesAvailable callback returns an error. |
| 1215 | + errorOnCallback := createPackageTestCase("error on callback", downloadSrv) |
| 1216 | + errorOnCallback.expectedStatus.Packages["package1"].Status = protobufs.PackageStatus_InstallFailed |
| 1217 | + errorOnCallback.expectedStatus.Packages["package1"].ErrorMessage = packageUpdateErrorMsg |
| 1218 | + errorOnCallback.errorOnCallback = true |
| 1219 | + tests = append(tests, errorOnCallback) |
| 1220 | + |
| 1221 | + for _, test := range tests { |
| 1222 | + t.Run(test.name, func(t *testing.T) { |
| 1223 | + verifyUpdatePackages(t, test) |
| 1224 | + }) |
| 1225 | + } |
| 1226 | +} |
0 commit comments