github.com/manicqin/nomad@v0.9.5/nomad/structs/diff_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestJobDiff(t *testing.T) {
    10  	cases := []struct {
    11  		Old, New   *Job
    12  		Expected   *JobDiff
    13  		Error      bool
    14  		Contextual bool
    15  	}{
    16  		{
    17  			Old: nil,
    18  			New: nil,
    19  			Expected: &JobDiff{
    20  				Type: DiffTypeNone,
    21  			},
    22  		},
    23  		{
    24  			// Different IDs
    25  			Old: &Job{
    26  				ID: "foo",
    27  			},
    28  			New: &Job{
    29  				ID: "bar",
    30  			},
    31  			Error: true,
    32  		},
    33  		{
    34  			// Primitive only that is the same
    35  			Old: &Job{
    36  				Region:    "foo",
    37  				ID:        "foo",
    38  				Name:      "foo",
    39  				Type:      "batch",
    40  				Priority:  10,
    41  				AllAtOnce: true,
    42  				Meta: map[string]string{
    43  					"foo": "bar",
    44  				},
    45  			},
    46  			New: &Job{
    47  				Region:    "foo",
    48  				ID:        "foo",
    49  				Name:      "foo",
    50  				Type:      "batch",
    51  				Priority:  10,
    52  				AllAtOnce: true,
    53  				Meta: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Expected: &JobDiff{
    58  				Type: DiffTypeNone,
    59  				ID:   "foo",
    60  			},
    61  		},
    62  		{
    63  			// Primitive only that is has diffs
    64  			Old: &Job{
    65  				Region:    "foo",
    66  				ID:        "foo",
    67  				Name:      "foo",
    68  				Type:      "batch",
    69  				Priority:  10,
    70  				AllAtOnce: true,
    71  				Meta: map[string]string{
    72  					"foo": "bar",
    73  				},
    74  			},
    75  			New: &Job{
    76  				Region:    "bar",
    77  				ID:        "foo",
    78  				Name:      "bar",
    79  				Type:      "system",
    80  				Priority:  100,
    81  				AllAtOnce: false,
    82  				Meta: map[string]string{
    83  					"foo": "baz",
    84  				},
    85  			},
    86  			Expected: &JobDiff{
    87  				Type: DiffTypeEdited,
    88  				ID:   "foo",
    89  				Fields: []*FieldDiff{
    90  					{
    91  						Type: DiffTypeEdited,
    92  						Name: "AllAtOnce",
    93  						Old:  "true",
    94  						New:  "false",
    95  					},
    96  					{
    97  						Type: DiffTypeEdited,
    98  						Name: "Meta[foo]",
    99  						Old:  "bar",
   100  						New:  "baz",
   101  					},
   102  					{
   103  						Type: DiffTypeEdited,
   104  						Name: "Name",
   105  						Old:  "foo",
   106  						New:  "bar",
   107  					},
   108  					{
   109  						Type: DiffTypeEdited,
   110  						Name: "Priority",
   111  						Old:  "10",
   112  						New:  "100",
   113  					},
   114  					{
   115  						Type: DiffTypeEdited,
   116  						Name: "Region",
   117  						Old:  "foo",
   118  						New:  "bar",
   119  					},
   120  					{
   121  						Type: DiffTypeEdited,
   122  						Name: "Type",
   123  						Old:  "batch",
   124  						New:  "system",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			// Primitive only deleted job
   131  			Old: &Job{
   132  				Region:    "foo",
   133  				ID:        "foo",
   134  				Name:      "foo",
   135  				Type:      "batch",
   136  				Priority:  10,
   137  				AllAtOnce: true,
   138  				Meta: map[string]string{
   139  					"foo": "bar",
   140  				},
   141  			},
   142  			New: nil,
   143  			Expected: &JobDiff{
   144  				Type: DiffTypeDeleted,
   145  				ID:   "foo",
   146  				Fields: []*FieldDiff{
   147  					{
   148  						Type: DiffTypeDeleted,
   149  						Name: "AllAtOnce",
   150  						Old:  "true",
   151  						New:  "",
   152  					},
   153  					{
   154  						Type: DiffTypeDeleted,
   155  						Name: "Dispatched",
   156  						Old:  "false",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Meta[foo]",
   162  						Old:  "bar",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Name",
   168  						Old:  "foo",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Priority",
   174  						Old:  "10",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Region",
   180  						Old:  "foo",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Stop",
   186  						Old:  "false",
   187  						New:  "",
   188  					},
   189  					{
   190  						Type: DiffTypeDeleted,
   191  						Name: "Type",
   192  						Old:  "batch",
   193  						New:  "",
   194  					},
   195  				},
   196  			},
   197  		},
   198  		{
   199  			// Primitive only added job
   200  			Old: nil,
   201  			New: &Job{
   202  				Region:    "foo",
   203  				ID:        "foo",
   204  				Name:      "foo",
   205  				Type:      "batch",
   206  				Priority:  10,
   207  				AllAtOnce: true,
   208  				Meta: map[string]string{
   209  					"foo": "bar",
   210  				},
   211  			},
   212  			Expected: &JobDiff{
   213  				Type: DiffTypeAdded,
   214  				ID:   "foo",
   215  				Fields: []*FieldDiff{
   216  					{
   217  						Type: DiffTypeAdded,
   218  						Name: "AllAtOnce",
   219  						Old:  "",
   220  						New:  "true",
   221  					},
   222  					{
   223  						Type: DiffTypeAdded,
   224  						Name: "Dispatched",
   225  						Old:  "",
   226  						New:  "false",
   227  					},
   228  					{
   229  						Type: DiffTypeAdded,
   230  						Name: "Meta[foo]",
   231  						Old:  "",
   232  						New:  "bar",
   233  					},
   234  					{
   235  						Type: DiffTypeAdded,
   236  						Name: "Name",
   237  						Old:  "",
   238  						New:  "foo",
   239  					},
   240  					{
   241  						Type: DiffTypeAdded,
   242  						Name: "Priority",
   243  						Old:  "",
   244  						New:  "10",
   245  					},
   246  					{
   247  						Type: DiffTypeAdded,
   248  						Name: "Region",
   249  						Old:  "",
   250  						New:  "foo",
   251  					},
   252  					{
   253  						Type: DiffTypeAdded,
   254  						Name: "Stop",
   255  						Old:  "",
   256  						New:  "false",
   257  					},
   258  					{
   259  						Type: DiffTypeAdded,
   260  						Name: "Type",
   261  						Old:  "",
   262  						New:  "batch",
   263  					},
   264  				},
   265  			},
   266  		},
   267  		{
   268  			// Map diff
   269  			Old: &Job{
   270  				Meta: map[string]string{
   271  					"foo": "foo",
   272  					"bar": "bar",
   273  				},
   274  			},
   275  			New: &Job{
   276  				Meta: map[string]string{
   277  					"bar": "bar",
   278  					"baz": "baz",
   279  				},
   280  			},
   281  			Expected: &JobDiff{
   282  				Type: DiffTypeEdited,
   283  				Fields: []*FieldDiff{
   284  					{
   285  						Type: DiffTypeAdded,
   286  						Name: "Meta[baz]",
   287  						Old:  "",
   288  						New:  "baz",
   289  					},
   290  					{
   291  						Type: DiffTypeDeleted,
   292  						Name: "Meta[foo]",
   293  						Old:  "foo",
   294  						New:  "",
   295  					},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			// Datacenter diff both added and removed
   301  			Old: &Job{
   302  				Datacenters: []string{"foo", "bar"},
   303  			},
   304  			New: &Job{
   305  				Datacenters: []string{"baz", "bar"},
   306  			},
   307  			Expected: &JobDiff{
   308  				Type: DiffTypeEdited,
   309  				Objects: []*ObjectDiff{
   310  					{
   311  						Type: DiffTypeEdited,
   312  						Name: "Datacenters",
   313  						Fields: []*FieldDiff{
   314  							{
   315  								Type: DiffTypeAdded,
   316  								Name: "Datacenters",
   317  								Old:  "",
   318  								New:  "baz",
   319  							},
   320  							{
   321  								Type: DiffTypeDeleted,
   322  								Name: "Datacenters",
   323  								Old:  "foo",
   324  								New:  "",
   325  							},
   326  						},
   327  					},
   328  				},
   329  			},
   330  		},
   331  		{
   332  			// Datacenter diff just added
   333  			Old: &Job{
   334  				Datacenters: []string{"foo", "bar"},
   335  			},
   336  			New: &Job{
   337  				Datacenters: []string{"foo", "bar", "baz"},
   338  			},
   339  			Expected: &JobDiff{
   340  				Type: DiffTypeEdited,
   341  				Objects: []*ObjectDiff{
   342  					{
   343  						Type: DiffTypeAdded,
   344  						Name: "Datacenters",
   345  						Fields: []*FieldDiff{
   346  							{
   347  								Type: DiffTypeAdded,
   348  								Name: "Datacenters",
   349  								Old:  "",
   350  								New:  "baz",
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			// Datacenter diff just deleted
   359  			Old: &Job{
   360  				Datacenters: []string{"foo", "bar"},
   361  			},
   362  			New: &Job{
   363  				Datacenters: []string{"foo"},
   364  			},
   365  			Expected: &JobDiff{
   366  				Type: DiffTypeEdited,
   367  				Objects: []*ObjectDiff{
   368  					{
   369  						Type: DiffTypeDeleted,
   370  						Name: "Datacenters",
   371  						Fields: []*FieldDiff{
   372  							{
   373  								Type: DiffTypeDeleted,
   374  								Name: "Datacenters",
   375  								Old:  "bar",
   376  								New:  "",
   377  							},
   378  						},
   379  					},
   380  				},
   381  			},
   382  		},
   383  		{
   384  			// Datacenter contextual no change
   385  			Contextual: true,
   386  			Old: &Job{
   387  				Datacenters: []string{"foo", "bar"},
   388  			},
   389  			New: &Job{
   390  				Datacenters: []string{"foo", "bar"},
   391  			},
   392  			Expected: &JobDiff{
   393  				Type: DiffTypeNone,
   394  			},
   395  		},
   396  		{
   397  			// Datacenter contextual
   398  			Contextual: true,
   399  			Old: &Job{
   400  				Datacenters: []string{"foo", "bar"},
   401  			},
   402  			New: &Job{
   403  				Datacenters: []string{"foo", "bar", "baz"},
   404  			},
   405  			Expected: &JobDiff{
   406  				Type: DiffTypeEdited,
   407  				Objects: []*ObjectDiff{
   408  					{
   409  						Type: DiffTypeAdded,
   410  						Name: "Datacenters",
   411  						Fields: []*FieldDiff{
   412  							{
   413  								Type: DiffTypeAdded,
   414  								Name: "Datacenters",
   415  								Old:  "",
   416  								New:  "baz",
   417  							},
   418  							{
   419  								Type: DiffTypeNone,
   420  								Name: "Datacenters",
   421  								Old:  "bar",
   422  								New:  "bar",
   423  							},
   424  							{
   425  								Type: DiffTypeNone,
   426  								Name: "Datacenters",
   427  								Old:  "foo",
   428  								New:  "foo",
   429  							},
   430  						},
   431  					},
   432  				},
   433  			},
   434  		},
   435  		{
   436  			// Periodic added
   437  			Old: &Job{},
   438  			New: &Job{
   439  				Periodic: &PeriodicConfig{
   440  					Enabled:         false,
   441  					Spec:            "*/15 * * * * *",
   442  					SpecType:        "foo",
   443  					ProhibitOverlap: false,
   444  					TimeZone:        "Europe/Minsk",
   445  				},
   446  			},
   447  			Expected: &JobDiff{
   448  				Type: DiffTypeEdited,
   449  				Objects: []*ObjectDiff{
   450  					{
   451  						Type: DiffTypeAdded,
   452  						Name: "Periodic",
   453  						Fields: []*FieldDiff{
   454  							{
   455  								Type: DiffTypeAdded,
   456  								Name: "Enabled",
   457  								Old:  "",
   458  								New:  "false",
   459  							},
   460  							{
   461  								Type: DiffTypeAdded,
   462  								Name: "ProhibitOverlap",
   463  								Old:  "",
   464  								New:  "false",
   465  							},
   466  							{
   467  								Type: DiffTypeAdded,
   468  								Name: "Spec",
   469  								Old:  "",
   470  								New:  "*/15 * * * * *",
   471  							},
   472  							{
   473  								Type: DiffTypeAdded,
   474  								Name: "SpecType",
   475  								Old:  "",
   476  								New:  "foo",
   477  							},
   478  							{
   479  								Type: DiffTypeAdded,
   480  								Name: "TimeZone",
   481  								Old:  "",
   482  								New:  "Europe/Minsk",
   483  							},
   484  						},
   485  					},
   486  				},
   487  			},
   488  		},
   489  		{
   490  			// Periodic deleted
   491  			Old: &Job{
   492  				Periodic: &PeriodicConfig{
   493  					Enabled:         false,
   494  					Spec:            "*/15 * * * * *",
   495  					SpecType:        "foo",
   496  					ProhibitOverlap: false,
   497  					TimeZone:        "Europe/Minsk",
   498  				},
   499  			},
   500  			New: &Job{},
   501  			Expected: &JobDiff{
   502  				Type: DiffTypeEdited,
   503  				Objects: []*ObjectDiff{
   504  					{
   505  						Type: DiffTypeDeleted,
   506  						Name: "Periodic",
   507  						Fields: []*FieldDiff{
   508  							{
   509  								Type: DiffTypeDeleted,
   510  								Name: "Enabled",
   511  								Old:  "false",
   512  								New:  "",
   513  							},
   514  							{
   515  								Type: DiffTypeDeleted,
   516  								Name: "ProhibitOverlap",
   517  								Old:  "false",
   518  								New:  "",
   519  							},
   520  							{
   521  								Type: DiffTypeDeleted,
   522  								Name: "Spec",
   523  								Old:  "*/15 * * * * *",
   524  								New:  "",
   525  							},
   526  							{
   527  								Type: DiffTypeDeleted,
   528  								Name: "SpecType",
   529  								Old:  "foo",
   530  								New:  "",
   531  							},
   532  							{
   533  								Type: DiffTypeDeleted,
   534  								Name: "TimeZone",
   535  								Old:  "Europe/Minsk",
   536  								New:  "",
   537  							},
   538  						},
   539  					},
   540  				},
   541  			},
   542  		},
   543  		{
   544  			// Periodic edited
   545  			Old: &Job{
   546  				Periodic: &PeriodicConfig{
   547  					Enabled:         false,
   548  					Spec:            "*/15 * * * * *",
   549  					SpecType:        "foo",
   550  					ProhibitOverlap: false,
   551  					TimeZone:        "Europe/Minsk",
   552  				},
   553  			},
   554  			New: &Job{
   555  				Periodic: &PeriodicConfig{
   556  					Enabled:         true,
   557  					Spec:            "* * * * * *",
   558  					SpecType:        "cron",
   559  					ProhibitOverlap: true,
   560  					TimeZone:        "America/Los_Angeles",
   561  				},
   562  			},
   563  			Expected: &JobDiff{
   564  				Type: DiffTypeEdited,
   565  				Objects: []*ObjectDiff{
   566  					{
   567  						Type: DiffTypeEdited,
   568  						Name: "Periodic",
   569  						Fields: []*FieldDiff{
   570  							{
   571  								Type: DiffTypeEdited,
   572  								Name: "Enabled",
   573  								Old:  "false",
   574  								New:  "true",
   575  							},
   576  							{
   577  								Type: DiffTypeEdited,
   578  								Name: "ProhibitOverlap",
   579  								Old:  "false",
   580  								New:  "true",
   581  							},
   582  							{
   583  								Type: DiffTypeEdited,
   584  								Name: "Spec",
   585  								Old:  "*/15 * * * * *",
   586  								New:  "* * * * * *",
   587  							},
   588  							{
   589  								Type: DiffTypeEdited,
   590  								Name: "SpecType",
   591  								Old:  "foo",
   592  								New:  "cron",
   593  							},
   594  							{
   595  								Type: DiffTypeEdited,
   596  								Name: "TimeZone",
   597  								Old:  "Europe/Minsk",
   598  								New:  "America/Los_Angeles",
   599  							},
   600  						},
   601  					},
   602  				},
   603  			},
   604  		},
   605  		{
   606  			// Periodic edited with context
   607  			Contextual: true,
   608  			Old: &Job{
   609  				Periodic: &PeriodicConfig{
   610  					Enabled:         false,
   611  					Spec:            "*/15 * * * * *",
   612  					SpecType:        "foo",
   613  					ProhibitOverlap: false,
   614  					TimeZone:        "Europe/Minsk",
   615  				},
   616  			},
   617  			New: &Job{
   618  				Periodic: &PeriodicConfig{
   619  					Enabled:         true,
   620  					Spec:            "* * * * * *",
   621  					SpecType:        "foo",
   622  					ProhibitOverlap: false,
   623  					TimeZone:        "Europe/Minsk",
   624  				},
   625  			},
   626  			Expected: &JobDiff{
   627  				Type: DiffTypeEdited,
   628  				Objects: []*ObjectDiff{
   629  					{
   630  						Type: DiffTypeEdited,
   631  						Name: "Periodic",
   632  						Fields: []*FieldDiff{
   633  							{
   634  								Type: DiffTypeEdited,
   635  								Name: "Enabled",
   636  								Old:  "false",
   637  								New:  "true",
   638  							},
   639  							{
   640  								Type: DiffTypeNone,
   641  								Name: "ProhibitOverlap",
   642  								Old:  "false",
   643  								New:  "false",
   644  							},
   645  							{
   646  								Type: DiffTypeEdited,
   647  								Name: "Spec",
   648  								Old:  "*/15 * * * * *",
   649  								New:  "* * * * * *",
   650  							},
   651  							{
   652  								Type: DiffTypeNone,
   653  								Name: "SpecType",
   654  								Old:  "foo",
   655  								New:  "foo",
   656  							},
   657  							{
   658  								Type: DiffTypeNone,
   659  								Name: "TimeZone",
   660  								Old:  "Europe/Minsk",
   661  								New:  "Europe/Minsk",
   662  							},
   663  						},
   664  					},
   665  				},
   666  			},
   667  		},
   668  		{
   669  			// Constraints edited
   670  			Old: &Job{
   671  				Constraints: []*Constraint{
   672  					{
   673  						LTarget: "foo",
   674  						RTarget: "foo",
   675  						Operand: "foo",
   676  						str:     "foo",
   677  					},
   678  					{
   679  						LTarget: "bar",
   680  						RTarget: "bar",
   681  						Operand: "bar",
   682  						str:     "bar",
   683  					},
   684  				},
   685  			},
   686  			New: &Job{
   687  				Constraints: []*Constraint{
   688  					{
   689  						LTarget: "foo",
   690  						RTarget: "foo",
   691  						Operand: "foo",
   692  						str:     "foo",
   693  					},
   694  					{
   695  						LTarget: "baz",
   696  						RTarget: "baz",
   697  						Operand: "baz",
   698  						str:     "baz",
   699  					},
   700  				},
   701  			},
   702  			Expected: &JobDiff{
   703  				Type: DiffTypeEdited,
   704  				Objects: []*ObjectDiff{
   705  					{
   706  						Type: DiffTypeAdded,
   707  						Name: "Constraint",
   708  						Fields: []*FieldDiff{
   709  							{
   710  								Type: DiffTypeAdded,
   711  								Name: "LTarget",
   712  								Old:  "",
   713  								New:  "baz",
   714  							},
   715  							{
   716  								Type: DiffTypeAdded,
   717  								Name: "Operand",
   718  								Old:  "",
   719  								New:  "baz",
   720  							},
   721  							{
   722  								Type: DiffTypeAdded,
   723  								Name: "RTarget",
   724  								Old:  "",
   725  								New:  "baz",
   726  							},
   727  						},
   728  					},
   729  					{
   730  						Type: DiffTypeDeleted,
   731  						Name: "Constraint",
   732  						Fields: []*FieldDiff{
   733  							{
   734  								Type: DiffTypeDeleted,
   735  								Name: "LTarget",
   736  								Old:  "bar",
   737  								New:  "",
   738  							},
   739  							{
   740  								Type: DiffTypeDeleted,
   741  								Name: "Operand",
   742  								Old:  "bar",
   743  								New:  "",
   744  							},
   745  							{
   746  								Type: DiffTypeDeleted,
   747  								Name: "RTarget",
   748  								Old:  "bar",
   749  								New:  "",
   750  							},
   751  						},
   752  					},
   753  				},
   754  			},
   755  		},
   756  		{
   757  			// Affinities edited
   758  			Old: &Job{
   759  				Affinities: []*Affinity{
   760  					{
   761  						LTarget: "foo",
   762  						RTarget: "foo",
   763  						Operand: "foo",
   764  						Weight:  20,
   765  						str:     "foo",
   766  					},
   767  					{
   768  						LTarget: "bar",
   769  						RTarget: "bar",
   770  						Operand: "bar",
   771  						Weight:  20,
   772  						str:     "bar",
   773  					},
   774  				},
   775  			},
   776  			New: &Job{
   777  				Affinities: []*Affinity{
   778  					{
   779  						LTarget: "foo",
   780  						RTarget: "foo",
   781  						Operand: "foo",
   782  						Weight:  20,
   783  						str:     "foo",
   784  					},
   785  					{
   786  						LTarget: "baz",
   787  						RTarget: "baz",
   788  						Operand: "baz",
   789  						Weight:  20,
   790  						str:     "baz",
   791  					},
   792  				},
   793  			},
   794  			Expected: &JobDiff{
   795  				Type: DiffTypeEdited,
   796  				Objects: []*ObjectDiff{
   797  					{
   798  						Type: DiffTypeAdded,
   799  						Name: "Affinity",
   800  						Fields: []*FieldDiff{
   801  							{
   802  								Type: DiffTypeAdded,
   803  								Name: "LTarget",
   804  								Old:  "",
   805  								New:  "baz",
   806  							},
   807  							{
   808  								Type: DiffTypeAdded,
   809  								Name: "Operand",
   810  								Old:  "",
   811  								New:  "baz",
   812  							},
   813  							{
   814  								Type: DiffTypeAdded,
   815  								Name: "RTarget",
   816  								Old:  "",
   817  								New:  "baz",
   818  							},
   819  							{
   820  								Type: DiffTypeAdded,
   821  								Name: "Weight",
   822  								Old:  "",
   823  								New:  "20",
   824  							},
   825  						},
   826  					},
   827  					{
   828  						Type: DiffTypeDeleted,
   829  						Name: "Affinity",
   830  						Fields: []*FieldDiff{
   831  							{
   832  								Type: DiffTypeDeleted,
   833  								Name: "LTarget",
   834  								Old:  "bar",
   835  								New:  "",
   836  							},
   837  							{
   838  								Type: DiffTypeDeleted,
   839  								Name: "Operand",
   840  								Old:  "bar",
   841  								New:  "",
   842  							},
   843  							{
   844  								Type: DiffTypeDeleted,
   845  								Name: "RTarget",
   846  								Old:  "bar",
   847  								New:  "",
   848  							},
   849  							{
   850  								Type: DiffTypeDeleted,
   851  								Name: "Weight",
   852  								Old:  "20",
   853  								New:  "",
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  		},
   860  		{
   861  			// Task groups edited
   862  			Old: &Job{
   863  				TaskGroups: []*TaskGroup{
   864  					{
   865  						Name:  "foo",
   866  						Count: 1,
   867  					},
   868  					{
   869  						Name:  "bar",
   870  						Count: 1,
   871  					},
   872  					{
   873  						Name:  "baz",
   874  						Count: 1,
   875  					},
   876  				},
   877  			},
   878  			New: &Job{
   879  				TaskGroups: []*TaskGroup{
   880  					{
   881  						Name:  "bar",
   882  						Count: 1,
   883  					},
   884  					{
   885  						Name:  "baz",
   886  						Count: 2,
   887  					},
   888  					{
   889  						Name:  "bam",
   890  						Count: 1,
   891  					},
   892  				},
   893  			},
   894  			Expected: &JobDiff{
   895  				Type: DiffTypeEdited,
   896  				TaskGroups: []*TaskGroupDiff{
   897  					{
   898  						Type: DiffTypeAdded,
   899  						Name: "bam",
   900  						Fields: []*FieldDiff{
   901  							{
   902  								Type: DiffTypeAdded,
   903  								Name: "Count",
   904  								Old:  "",
   905  								New:  "1",
   906  							},
   907  						},
   908  					},
   909  					{
   910  						Type: DiffTypeNone,
   911  						Name: "bar",
   912  					},
   913  					{
   914  						Type: DiffTypeEdited,
   915  						Name: "baz",
   916  						Fields: []*FieldDiff{
   917  							{
   918  								Type: DiffTypeEdited,
   919  								Name: "Count",
   920  								Old:  "1",
   921  								New:  "2",
   922  							},
   923  						},
   924  					},
   925  					{
   926  						Type: DiffTypeDeleted,
   927  						Name: "foo",
   928  						Fields: []*FieldDiff{
   929  							{
   930  								Type: DiffTypeDeleted,
   931  								Name: "Count",
   932  								Old:  "1",
   933  								New:  "",
   934  							},
   935  						},
   936  					},
   937  				},
   938  			},
   939  		},
   940  		{
   941  			// Parameterized Job added
   942  			Old: &Job{},
   943  			New: &Job{
   944  				ParameterizedJob: &ParameterizedJobConfig{
   945  					Payload:      DispatchPayloadRequired,
   946  					MetaOptional: []string{"foo"},
   947  					MetaRequired: []string{"bar"},
   948  				},
   949  			},
   950  			Expected: &JobDiff{
   951  				Type: DiffTypeEdited,
   952  				Objects: []*ObjectDiff{
   953  					{
   954  						Type: DiffTypeAdded,
   955  						Name: "ParameterizedJob",
   956  						Fields: []*FieldDiff{
   957  							{
   958  								Type: DiffTypeAdded,
   959  								Name: "Payload",
   960  								Old:  "",
   961  								New:  DispatchPayloadRequired,
   962  							},
   963  						},
   964  						Objects: []*ObjectDiff{
   965  							{
   966  								Type: DiffTypeAdded,
   967  								Name: "MetaOptional",
   968  								Fields: []*FieldDiff{
   969  									{
   970  										Type: DiffTypeAdded,
   971  										Name: "MetaOptional",
   972  										Old:  "",
   973  										New:  "foo",
   974  									},
   975  								},
   976  							},
   977  							{
   978  								Type: DiffTypeAdded,
   979  								Name: "MetaRequired",
   980  								Fields: []*FieldDiff{
   981  									{
   982  										Type: DiffTypeAdded,
   983  										Name: "MetaRequired",
   984  										Old:  "",
   985  										New:  "bar",
   986  									},
   987  								},
   988  							},
   989  						},
   990  					},
   991  				},
   992  			},
   993  		},
   994  		{
   995  			// Parameterized Job deleted
   996  			Old: &Job{
   997  				ParameterizedJob: &ParameterizedJobConfig{
   998  					Payload:      DispatchPayloadRequired,
   999  					MetaOptional: []string{"foo"},
  1000  					MetaRequired: []string{"bar"},
  1001  				},
  1002  			},
  1003  			New: &Job{},
  1004  			Expected: &JobDiff{
  1005  				Type: DiffTypeEdited,
  1006  				Objects: []*ObjectDiff{
  1007  					{
  1008  						Type: DiffTypeDeleted,
  1009  						Name: "ParameterizedJob",
  1010  						Fields: []*FieldDiff{
  1011  							{
  1012  								Type: DiffTypeDeleted,
  1013  								Name: "Payload",
  1014  								Old:  DispatchPayloadRequired,
  1015  								New:  "",
  1016  							},
  1017  						},
  1018  						Objects: []*ObjectDiff{
  1019  							{
  1020  								Type: DiffTypeDeleted,
  1021  								Name: "MetaOptional",
  1022  								Fields: []*FieldDiff{
  1023  									{
  1024  										Type: DiffTypeDeleted,
  1025  										Name: "MetaOptional",
  1026  										Old:  "foo",
  1027  										New:  "",
  1028  									},
  1029  								},
  1030  							},
  1031  							{
  1032  								Type: DiffTypeDeleted,
  1033  								Name: "MetaRequired",
  1034  								Fields: []*FieldDiff{
  1035  									{
  1036  										Type: DiffTypeDeleted,
  1037  										Name: "MetaRequired",
  1038  										Old:  "bar",
  1039  										New:  "",
  1040  									},
  1041  								},
  1042  							},
  1043  						},
  1044  					},
  1045  				},
  1046  			},
  1047  		},
  1048  		{
  1049  			// Parameterized Job edited
  1050  			Old: &Job{
  1051  				ParameterizedJob: &ParameterizedJobConfig{
  1052  					Payload:      DispatchPayloadRequired,
  1053  					MetaOptional: []string{"foo"},
  1054  					MetaRequired: []string{"bar"},
  1055  				},
  1056  			},
  1057  			New: &Job{
  1058  				ParameterizedJob: &ParameterizedJobConfig{
  1059  					Payload:      DispatchPayloadOptional,
  1060  					MetaOptional: []string{"bam"},
  1061  					MetaRequired: []string{"bang"},
  1062  				},
  1063  			},
  1064  			Expected: &JobDiff{
  1065  				Type: DiffTypeEdited,
  1066  				Objects: []*ObjectDiff{
  1067  					{
  1068  						Type: DiffTypeEdited,
  1069  						Name: "ParameterizedJob",
  1070  						Fields: []*FieldDiff{
  1071  							{
  1072  								Type: DiffTypeEdited,
  1073  								Name: "Payload",
  1074  								Old:  DispatchPayloadRequired,
  1075  								New:  DispatchPayloadOptional,
  1076  							},
  1077  						},
  1078  						Objects: []*ObjectDiff{
  1079  							{
  1080  								Type: DiffTypeEdited,
  1081  								Name: "MetaOptional",
  1082  								Fields: []*FieldDiff{
  1083  									{
  1084  										Type: DiffTypeAdded,
  1085  										Name: "MetaOptional",
  1086  										Old:  "",
  1087  										New:  "bam",
  1088  									},
  1089  									{
  1090  										Type: DiffTypeDeleted,
  1091  										Name: "MetaOptional",
  1092  										Old:  "foo",
  1093  										New:  "",
  1094  									},
  1095  								},
  1096  							},
  1097  							{
  1098  								Type: DiffTypeEdited,
  1099  								Name: "MetaRequired",
  1100  								Fields: []*FieldDiff{
  1101  									{
  1102  										Type: DiffTypeAdded,
  1103  										Name: "MetaRequired",
  1104  										Old:  "",
  1105  										New:  "bang",
  1106  									},
  1107  									{
  1108  										Type: DiffTypeDeleted,
  1109  										Name: "MetaRequired",
  1110  										Old:  "bar",
  1111  										New:  "",
  1112  									},
  1113  								},
  1114  							},
  1115  						},
  1116  					},
  1117  				},
  1118  			},
  1119  		},
  1120  		{
  1121  			// Parameterized Job edited with context
  1122  			Contextual: true,
  1123  			Old: &Job{
  1124  				ParameterizedJob: &ParameterizedJobConfig{
  1125  					Payload:      DispatchPayloadRequired,
  1126  					MetaOptional: []string{"foo"},
  1127  					MetaRequired: []string{"bar"},
  1128  				},
  1129  			},
  1130  			New: &Job{
  1131  				ParameterizedJob: &ParameterizedJobConfig{
  1132  					Payload:      DispatchPayloadOptional,
  1133  					MetaOptional: []string{"foo"},
  1134  					MetaRequired: []string{"bar"},
  1135  				},
  1136  			},
  1137  			Expected: &JobDiff{
  1138  				Type: DiffTypeEdited,
  1139  				Objects: []*ObjectDiff{
  1140  					{
  1141  						Type: DiffTypeEdited,
  1142  						Name: "ParameterizedJob",
  1143  						Fields: []*FieldDiff{
  1144  							{
  1145  								Type: DiffTypeEdited,
  1146  								Name: "Payload",
  1147  								Old:  DispatchPayloadRequired,
  1148  								New:  DispatchPayloadOptional,
  1149  							},
  1150  						},
  1151  						Objects: []*ObjectDiff{
  1152  							{
  1153  								Type: DiffTypeNone,
  1154  								Name: "MetaOptional",
  1155  								Fields: []*FieldDiff{
  1156  									{
  1157  										Type: DiffTypeNone,
  1158  										Name: "MetaOptional",
  1159  										Old:  "foo",
  1160  										New:  "foo",
  1161  									},
  1162  								},
  1163  							},
  1164  							{
  1165  								Type: DiffTypeNone,
  1166  								Name: "MetaRequired",
  1167  								Fields: []*FieldDiff{
  1168  									{
  1169  										Type: DiffTypeNone,
  1170  										Name: "MetaRequired",
  1171  										Old:  "bar",
  1172  										New:  "bar",
  1173  									},
  1174  								},
  1175  							},
  1176  						},
  1177  					},
  1178  				},
  1179  			},
  1180  		},
  1181  	}
  1182  
  1183  	for i, c := range cases {
  1184  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1185  		if c.Error && err == nil {
  1186  			t.Fatalf("case %d: expected errored", i+1)
  1187  		} else if err != nil {
  1188  			if !c.Error {
  1189  				t.Fatalf("case %d: errored %#v", i+1, err)
  1190  			} else {
  1191  				continue
  1192  			}
  1193  		}
  1194  
  1195  		if !reflect.DeepEqual(actual, c.Expected) {
  1196  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1197  				i+1, actual, c.Expected)
  1198  		}
  1199  	}
  1200  }
  1201  
  1202  func TestTaskGroupDiff(t *testing.T) {
  1203  	cases := []struct {
  1204  		Old, New   *TaskGroup
  1205  		Expected   *TaskGroupDiff
  1206  		Error      bool
  1207  		Contextual bool
  1208  	}{
  1209  		{
  1210  			Old: nil,
  1211  			New: nil,
  1212  			Expected: &TaskGroupDiff{
  1213  				Type: DiffTypeNone,
  1214  			},
  1215  		},
  1216  		{
  1217  			// Primitive only that has different names
  1218  			Old: &TaskGroup{
  1219  				Name:  "foo",
  1220  				Count: 10,
  1221  				Meta: map[string]string{
  1222  					"foo": "bar",
  1223  				},
  1224  			},
  1225  			New: &TaskGroup{
  1226  				Name:  "bar",
  1227  				Count: 10,
  1228  				Meta: map[string]string{
  1229  					"foo": "bar",
  1230  				},
  1231  			},
  1232  			Error: true,
  1233  		},
  1234  		{
  1235  			// Primitive only that is the same
  1236  			Old: &TaskGroup{
  1237  				Name:  "foo",
  1238  				Count: 10,
  1239  				Meta: map[string]string{
  1240  					"foo": "bar",
  1241  				},
  1242  			},
  1243  			New: &TaskGroup{
  1244  				Name:  "foo",
  1245  				Count: 10,
  1246  				Meta: map[string]string{
  1247  					"foo": "bar",
  1248  				},
  1249  			},
  1250  			Expected: &TaskGroupDiff{
  1251  				Type: DiffTypeNone,
  1252  				Name: "foo",
  1253  			},
  1254  		},
  1255  		{
  1256  			// Primitive only that has diffs
  1257  			Old: &TaskGroup{
  1258  				Name:  "foo",
  1259  				Count: 10,
  1260  				Meta: map[string]string{
  1261  					"foo": "bar",
  1262  				},
  1263  			},
  1264  			New: &TaskGroup{
  1265  				Name:  "foo",
  1266  				Count: 100,
  1267  				Meta: map[string]string{
  1268  					"foo": "baz",
  1269  				},
  1270  			},
  1271  			Expected: &TaskGroupDiff{
  1272  				Type: DiffTypeEdited,
  1273  				Name: "foo",
  1274  				Fields: []*FieldDiff{
  1275  					{
  1276  						Type: DiffTypeEdited,
  1277  						Name: "Count",
  1278  						Old:  "10",
  1279  						New:  "100",
  1280  					},
  1281  					{
  1282  						Type: DiffTypeEdited,
  1283  						Name: "Meta[foo]",
  1284  						Old:  "bar",
  1285  						New:  "baz",
  1286  					},
  1287  				},
  1288  			},
  1289  		},
  1290  		{
  1291  			// Map diff
  1292  			Old: &TaskGroup{
  1293  				Meta: map[string]string{
  1294  					"foo": "foo",
  1295  					"bar": "bar",
  1296  				},
  1297  			},
  1298  			New: &TaskGroup{
  1299  				Meta: map[string]string{
  1300  					"bar": "bar",
  1301  					"baz": "baz",
  1302  				},
  1303  			},
  1304  			Expected: &TaskGroupDiff{
  1305  				Type: DiffTypeEdited,
  1306  				Fields: []*FieldDiff{
  1307  					{
  1308  						Type: DiffTypeAdded,
  1309  						Name: "Meta[baz]",
  1310  						Old:  "",
  1311  						New:  "baz",
  1312  					},
  1313  					{
  1314  						Type: DiffTypeDeleted,
  1315  						Name: "Meta[foo]",
  1316  						Old:  "foo",
  1317  						New:  "",
  1318  					},
  1319  				},
  1320  			},
  1321  		},
  1322  		{
  1323  			// Constraints edited
  1324  			Old: &TaskGroup{
  1325  				Constraints: []*Constraint{
  1326  					{
  1327  						LTarget: "foo",
  1328  						RTarget: "foo",
  1329  						Operand: "foo",
  1330  						str:     "foo",
  1331  					},
  1332  					{
  1333  						LTarget: "bar",
  1334  						RTarget: "bar",
  1335  						Operand: "bar",
  1336  						str:     "bar",
  1337  					},
  1338  				},
  1339  			},
  1340  			New: &TaskGroup{
  1341  				Constraints: []*Constraint{
  1342  					{
  1343  						LTarget: "foo",
  1344  						RTarget: "foo",
  1345  						Operand: "foo",
  1346  						str:     "foo",
  1347  					},
  1348  					{
  1349  						LTarget: "baz",
  1350  						RTarget: "baz",
  1351  						Operand: "baz",
  1352  						str:     "baz",
  1353  					},
  1354  				},
  1355  			},
  1356  			Expected: &TaskGroupDiff{
  1357  				Type: DiffTypeEdited,
  1358  				Objects: []*ObjectDiff{
  1359  					{
  1360  						Type: DiffTypeAdded,
  1361  						Name: "Constraint",
  1362  						Fields: []*FieldDiff{
  1363  							{
  1364  								Type: DiffTypeAdded,
  1365  								Name: "LTarget",
  1366  								Old:  "",
  1367  								New:  "baz",
  1368  							},
  1369  							{
  1370  								Type: DiffTypeAdded,
  1371  								Name: "Operand",
  1372  								Old:  "",
  1373  								New:  "baz",
  1374  							},
  1375  							{
  1376  								Type: DiffTypeAdded,
  1377  								Name: "RTarget",
  1378  								Old:  "",
  1379  								New:  "baz",
  1380  							},
  1381  						},
  1382  					},
  1383  					{
  1384  						Type: DiffTypeDeleted,
  1385  						Name: "Constraint",
  1386  						Fields: []*FieldDiff{
  1387  							{
  1388  								Type: DiffTypeDeleted,
  1389  								Name: "LTarget",
  1390  								Old:  "bar",
  1391  								New:  "",
  1392  							},
  1393  							{
  1394  								Type: DiffTypeDeleted,
  1395  								Name: "Operand",
  1396  								Old:  "bar",
  1397  								New:  "",
  1398  							},
  1399  							{
  1400  								Type: DiffTypeDeleted,
  1401  								Name: "RTarget",
  1402  								Old:  "bar",
  1403  								New:  "",
  1404  							},
  1405  						},
  1406  					},
  1407  				},
  1408  			},
  1409  		},
  1410  		{
  1411  			// Affinities edited
  1412  			Old: &TaskGroup{
  1413  				Affinities: []*Affinity{
  1414  					{
  1415  						LTarget: "foo",
  1416  						RTarget: "foo",
  1417  						Operand: "foo",
  1418  						Weight:  20,
  1419  						str:     "foo",
  1420  					},
  1421  					{
  1422  						LTarget: "bar",
  1423  						RTarget: "bar",
  1424  						Operand: "bar",
  1425  						Weight:  20,
  1426  						str:     "bar",
  1427  					},
  1428  				},
  1429  			},
  1430  			New: &TaskGroup{
  1431  				Affinities: []*Affinity{
  1432  					{
  1433  						LTarget: "foo",
  1434  						RTarget: "foo",
  1435  						Operand: "foo",
  1436  						Weight:  20,
  1437  						str:     "foo",
  1438  					},
  1439  					{
  1440  						LTarget: "baz",
  1441  						RTarget: "baz",
  1442  						Operand: "baz",
  1443  						Weight:  20,
  1444  						str:     "baz",
  1445  					},
  1446  				},
  1447  			},
  1448  			Expected: &TaskGroupDiff{
  1449  				Type: DiffTypeEdited,
  1450  				Objects: []*ObjectDiff{
  1451  					{
  1452  						Type: DiffTypeAdded,
  1453  						Name: "Affinity",
  1454  						Fields: []*FieldDiff{
  1455  							{
  1456  								Type: DiffTypeAdded,
  1457  								Name: "LTarget",
  1458  								Old:  "",
  1459  								New:  "baz",
  1460  							},
  1461  							{
  1462  								Type: DiffTypeAdded,
  1463  								Name: "Operand",
  1464  								Old:  "",
  1465  								New:  "baz",
  1466  							},
  1467  							{
  1468  								Type: DiffTypeAdded,
  1469  								Name: "RTarget",
  1470  								Old:  "",
  1471  								New:  "baz",
  1472  							},
  1473  							{
  1474  								Type: DiffTypeAdded,
  1475  								Name: "Weight",
  1476  								Old:  "",
  1477  								New:  "20",
  1478  							},
  1479  						},
  1480  					},
  1481  					{
  1482  						Type: DiffTypeDeleted,
  1483  						Name: "Affinity",
  1484  						Fields: []*FieldDiff{
  1485  							{
  1486  								Type: DiffTypeDeleted,
  1487  								Name: "LTarget",
  1488  								Old:  "bar",
  1489  								New:  "",
  1490  							},
  1491  							{
  1492  								Type: DiffTypeDeleted,
  1493  								Name: "Operand",
  1494  								Old:  "bar",
  1495  								New:  "",
  1496  							},
  1497  							{
  1498  								Type: DiffTypeDeleted,
  1499  								Name: "RTarget",
  1500  								Old:  "bar",
  1501  								New:  "",
  1502  							},
  1503  							{
  1504  								Type: DiffTypeDeleted,
  1505  								Name: "Weight",
  1506  								Old:  "20",
  1507  								New:  "",
  1508  							},
  1509  						},
  1510  					},
  1511  				},
  1512  			},
  1513  		},
  1514  		{
  1515  			// RestartPolicy added
  1516  			Old: &TaskGroup{},
  1517  			New: &TaskGroup{
  1518  				RestartPolicy: &RestartPolicy{
  1519  					Attempts: 1,
  1520  					Interval: 1 * time.Second,
  1521  					Delay:    1 * time.Second,
  1522  					Mode:     "fail",
  1523  				},
  1524  			},
  1525  			Expected: &TaskGroupDiff{
  1526  				Type: DiffTypeEdited,
  1527  				Objects: []*ObjectDiff{
  1528  					{
  1529  						Type: DiffTypeAdded,
  1530  						Name: "RestartPolicy",
  1531  						Fields: []*FieldDiff{
  1532  							{
  1533  								Type: DiffTypeAdded,
  1534  								Name: "Attempts",
  1535  								Old:  "",
  1536  								New:  "1",
  1537  							},
  1538  							{
  1539  								Type: DiffTypeAdded,
  1540  								Name: "Delay",
  1541  								Old:  "",
  1542  								New:  "1000000000",
  1543  							},
  1544  							{
  1545  								Type: DiffTypeAdded,
  1546  								Name: "Interval",
  1547  								Old:  "",
  1548  								New:  "1000000000",
  1549  							},
  1550  							{
  1551  								Type: DiffTypeAdded,
  1552  								Name: "Mode",
  1553  								Old:  "",
  1554  								New:  "fail",
  1555  							},
  1556  						},
  1557  					},
  1558  				},
  1559  			},
  1560  		},
  1561  		{
  1562  			// RestartPolicy deleted
  1563  			Old: &TaskGroup{
  1564  				RestartPolicy: &RestartPolicy{
  1565  					Attempts: 1,
  1566  					Interval: 1 * time.Second,
  1567  					Delay:    1 * time.Second,
  1568  					Mode:     "fail",
  1569  				},
  1570  			},
  1571  			New: &TaskGroup{},
  1572  			Expected: &TaskGroupDiff{
  1573  				Type: DiffTypeEdited,
  1574  				Objects: []*ObjectDiff{
  1575  					{
  1576  						Type: DiffTypeDeleted,
  1577  						Name: "RestartPolicy",
  1578  						Fields: []*FieldDiff{
  1579  							{
  1580  								Type: DiffTypeDeleted,
  1581  								Name: "Attempts",
  1582  								Old:  "1",
  1583  								New:  "",
  1584  							},
  1585  							{
  1586  								Type: DiffTypeDeleted,
  1587  								Name: "Delay",
  1588  								Old:  "1000000000",
  1589  								New:  "",
  1590  							},
  1591  							{
  1592  								Type: DiffTypeDeleted,
  1593  								Name: "Interval",
  1594  								Old:  "1000000000",
  1595  								New:  "",
  1596  							},
  1597  							{
  1598  								Type: DiffTypeDeleted,
  1599  								Name: "Mode",
  1600  								Old:  "fail",
  1601  								New:  "",
  1602  							},
  1603  						},
  1604  					},
  1605  				},
  1606  			},
  1607  		},
  1608  		{
  1609  			// RestartPolicy edited
  1610  			Old: &TaskGroup{
  1611  				RestartPolicy: &RestartPolicy{
  1612  					Attempts: 1,
  1613  					Interval: 1 * time.Second,
  1614  					Delay:    1 * time.Second,
  1615  					Mode:     "fail",
  1616  				},
  1617  			},
  1618  			New: &TaskGroup{
  1619  				RestartPolicy: &RestartPolicy{
  1620  					Attempts: 2,
  1621  					Interval: 2 * time.Second,
  1622  					Delay:    2 * time.Second,
  1623  					Mode:     "delay",
  1624  				},
  1625  			},
  1626  			Expected: &TaskGroupDiff{
  1627  				Type: DiffTypeEdited,
  1628  				Objects: []*ObjectDiff{
  1629  					{
  1630  						Type: DiffTypeEdited,
  1631  						Name: "RestartPolicy",
  1632  						Fields: []*FieldDiff{
  1633  							{
  1634  								Type: DiffTypeEdited,
  1635  								Name: "Attempts",
  1636  								Old:  "1",
  1637  								New:  "2",
  1638  							},
  1639  							{
  1640  								Type: DiffTypeEdited,
  1641  								Name: "Delay",
  1642  								Old:  "1000000000",
  1643  								New:  "2000000000",
  1644  							},
  1645  							{
  1646  								Type: DiffTypeEdited,
  1647  								Name: "Interval",
  1648  								Old:  "1000000000",
  1649  								New:  "2000000000",
  1650  							},
  1651  							{
  1652  								Type: DiffTypeEdited,
  1653  								Name: "Mode",
  1654  								Old:  "fail",
  1655  								New:  "delay",
  1656  							},
  1657  						},
  1658  					},
  1659  				},
  1660  			},
  1661  		},
  1662  		{
  1663  			// RestartPolicy edited with context
  1664  			Contextual: true,
  1665  			Old: &TaskGroup{
  1666  				RestartPolicy: &RestartPolicy{
  1667  					Attempts: 1,
  1668  					Interval: 1 * time.Second,
  1669  					Delay:    1 * time.Second,
  1670  					Mode:     "fail",
  1671  				},
  1672  			},
  1673  			New: &TaskGroup{
  1674  				RestartPolicy: &RestartPolicy{
  1675  					Attempts: 2,
  1676  					Interval: 2 * time.Second,
  1677  					Delay:    1 * time.Second,
  1678  					Mode:     "fail",
  1679  				},
  1680  			},
  1681  			Expected: &TaskGroupDiff{
  1682  				Type: DiffTypeEdited,
  1683  				Objects: []*ObjectDiff{
  1684  					{
  1685  						Type: DiffTypeEdited,
  1686  						Name: "RestartPolicy",
  1687  						Fields: []*FieldDiff{
  1688  							{
  1689  								Type: DiffTypeEdited,
  1690  								Name: "Attempts",
  1691  								Old:  "1",
  1692  								New:  "2",
  1693  							},
  1694  							{
  1695  								Type: DiffTypeNone,
  1696  								Name: "Delay",
  1697  								Old:  "1000000000",
  1698  								New:  "1000000000",
  1699  							},
  1700  							{
  1701  								Type: DiffTypeEdited,
  1702  								Name: "Interval",
  1703  								Old:  "1000000000",
  1704  								New:  "2000000000",
  1705  							},
  1706  							{
  1707  								Type: DiffTypeNone,
  1708  								Name: "Mode",
  1709  								Old:  "fail",
  1710  								New:  "fail",
  1711  							},
  1712  						},
  1713  					},
  1714  				},
  1715  			},
  1716  		},
  1717  		{
  1718  			// ReschedulePolicy added
  1719  			Old: &TaskGroup{},
  1720  			New: &TaskGroup{
  1721  				ReschedulePolicy: &ReschedulePolicy{
  1722  					Attempts:      1,
  1723  					Interval:      15 * time.Second,
  1724  					Delay:         5 * time.Second,
  1725  					MaxDelay:      20 * time.Second,
  1726  					DelayFunction: "exponential",
  1727  					Unlimited:     false,
  1728  				},
  1729  			},
  1730  			Expected: &TaskGroupDiff{
  1731  				Type: DiffTypeEdited,
  1732  				Objects: []*ObjectDiff{
  1733  					{
  1734  						Type: DiffTypeAdded,
  1735  						Name: "ReschedulePolicy",
  1736  						Fields: []*FieldDiff{
  1737  							{
  1738  								Type: DiffTypeAdded,
  1739  								Name: "Attempts",
  1740  								Old:  "",
  1741  								New:  "1",
  1742  							},
  1743  							{
  1744  								Type: DiffTypeAdded,
  1745  								Name: "Delay",
  1746  								Old:  "",
  1747  								New:  "5000000000",
  1748  							},
  1749  							{
  1750  								Type: DiffTypeAdded,
  1751  								Name: "DelayFunction",
  1752  								Old:  "",
  1753  								New:  "exponential",
  1754  							},
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Interval",
  1758  								Old:  "",
  1759  								New:  "15000000000",
  1760  							},
  1761  							{
  1762  								Type: DiffTypeAdded,
  1763  								Name: "MaxDelay",
  1764  								Old:  "",
  1765  								New:  "20000000000",
  1766  							},
  1767  							{
  1768  								Type: DiffTypeAdded,
  1769  								Name: "Unlimited",
  1770  								Old:  "",
  1771  								New:  "false",
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			// ReschedulePolicy deleted
  1780  			Old: &TaskGroup{
  1781  				ReschedulePolicy: &ReschedulePolicy{
  1782  					Attempts:      1,
  1783  					Interval:      15 * time.Second,
  1784  					Delay:         5 * time.Second,
  1785  					MaxDelay:      20 * time.Second,
  1786  					DelayFunction: "exponential",
  1787  					Unlimited:     false,
  1788  				},
  1789  			},
  1790  			New: &TaskGroup{},
  1791  			Expected: &TaskGroupDiff{
  1792  				Type: DiffTypeEdited,
  1793  				Objects: []*ObjectDiff{
  1794  					{
  1795  						Type: DiffTypeDeleted,
  1796  						Name: "ReschedulePolicy",
  1797  						Fields: []*FieldDiff{
  1798  							{
  1799  								Type: DiffTypeDeleted,
  1800  								Name: "Attempts",
  1801  								Old:  "1",
  1802  								New:  "",
  1803  							},
  1804  							{
  1805  								Type: DiffTypeDeleted,
  1806  								Name: "Delay",
  1807  								Old:  "5000000000",
  1808  								New:  "",
  1809  							},
  1810  							{
  1811  								Type: DiffTypeDeleted,
  1812  								Name: "DelayFunction",
  1813  								Old:  "exponential",
  1814  								New:  "",
  1815  							},
  1816  							{
  1817  								Type: DiffTypeDeleted,
  1818  								Name: "Interval",
  1819  								Old:  "15000000000",
  1820  								New:  "",
  1821  							},
  1822  							{
  1823  								Type: DiffTypeDeleted,
  1824  								Name: "MaxDelay",
  1825  								Old:  "20000000000",
  1826  								New:  "",
  1827  							},
  1828  							{
  1829  								Type: DiffTypeDeleted,
  1830  								Name: "Unlimited",
  1831  								Old:  "false",
  1832  								New:  "",
  1833  							},
  1834  						},
  1835  					},
  1836  				},
  1837  			},
  1838  		},
  1839  		{
  1840  			// ReschedulePolicy edited
  1841  			Old: &TaskGroup{
  1842  				ReschedulePolicy: &ReschedulePolicy{
  1843  					Attempts:      1,
  1844  					Interval:      1 * time.Second,
  1845  					DelayFunction: "exponential",
  1846  					Delay:         20 * time.Second,
  1847  					MaxDelay:      1 * time.Minute,
  1848  					Unlimited:     false,
  1849  				},
  1850  			},
  1851  			New: &TaskGroup{
  1852  				ReschedulePolicy: &ReschedulePolicy{
  1853  					Attempts:      2,
  1854  					Interval:      2 * time.Second,
  1855  					DelayFunction: "constant",
  1856  					Delay:         30 * time.Second,
  1857  					MaxDelay:      1 * time.Minute,
  1858  					Unlimited:     true,
  1859  				},
  1860  			},
  1861  			Expected: &TaskGroupDiff{
  1862  				Type: DiffTypeEdited,
  1863  				Objects: []*ObjectDiff{
  1864  					{
  1865  						Type: DiffTypeEdited,
  1866  						Name: "ReschedulePolicy",
  1867  						Fields: []*FieldDiff{
  1868  							{
  1869  								Type: DiffTypeEdited,
  1870  								Name: "Attempts",
  1871  								Old:  "1",
  1872  								New:  "2",
  1873  							},
  1874  							{
  1875  								Type: DiffTypeEdited,
  1876  								Name: "Delay",
  1877  								Old:  "20000000000",
  1878  								New:  "30000000000",
  1879  							},
  1880  							{
  1881  								Type: DiffTypeEdited,
  1882  								Name: "DelayFunction",
  1883  								Old:  "exponential",
  1884  								New:  "constant",
  1885  							},
  1886  							{
  1887  								Type: DiffTypeEdited,
  1888  								Name: "Interval",
  1889  								Old:  "1000000000",
  1890  								New:  "2000000000",
  1891  							},
  1892  							{
  1893  								Type: DiffTypeEdited,
  1894  								Name: "Unlimited",
  1895  								Old:  "false",
  1896  								New:  "true",
  1897  							},
  1898  						},
  1899  					},
  1900  				},
  1901  			},
  1902  		}, {
  1903  			// ReschedulePolicy edited with context
  1904  			Contextual: true,
  1905  			Old: &TaskGroup{
  1906  				ReschedulePolicy: &ReschedulePolicy{
  1907  					Attempts: 1,
  1908  					Interval: 1 * time.Second,
  1909  				},
  1910  			},
  1911  			New: &TaskGroup{
  1912  				ReschedulePolicy: &ReschedulePolicy{
  1913  					Attempts: 1,
  1914  					Interval: 2 * time.Second,
  1915  				},
  1916  			},
  1917  			Expected: &TaskGroupDiff{
  1918  				Type: DiffTypeEdited,
  1919  				Objects: []*ObjectDiff{
  1920  					{
  1921  						Type: DiffTypeEdited,
  1922  						Name: "ReschedulePolicy",
  1923  						Fields: []*FieldDiff{
  1924  							{
  1925  								Type: DiffTypeNone,
  1926  								Name: "Attempts",
  1927  								Old:  "1",
  1928  								New:  "1",
  1929  							},
  1930  							{
  1931  								Type: DiffTypeNone,
  1932  								Name: "Delay",
  1933  								Old:  "0",
  1934  								New:  "0",
  1935  							},
  1936  							{
  1937  								Type: DiffTypeNone,
  1938  								Name: "DelayFunction",
  1939  								Old:  "",
  1940  								New:  "",
  1941  							},
  1942  							{
  1943  								Type: DiffTypeEdited,
  1944  								Name: "Interval",
  1945  								Old:  "1000000000",
  1946  								New:  "2000000000",
  1947  							},
  1948  							{
  1949  								Type: DiffTypeNone,
  1950  								Name: "MaxDelay",
  1951  								Old:  "0",
  1952  								New:  "0",
  1953  							},
  1954  							{
  1955  								Type: DiffTypeNone,
  1956  								Name: "Unlimited",
  1957  								Old:  "false",
  1958  								New:  "false",
  1959  							},
  1960  						},
  1961  					},
  1962  				},
  1963  			},
  1964  		},
  1965  		{
  1966  			// Update strategy deleted
  1967  			Old: &TaskGroup{
  1968  				Update: &UpdateStrategy{
  1969  					AutoRevert: true,
  1970  				},
  1971  			},
  1972  			New: &TaskGroup{},
  1973  			Expected: &TaskGroupDiff{
  1974  				Type: DiffTypeEdited,
  1975  				Objects: []*ObjectDiff{
  1976  					{
  1977  						Type: DiffTypeDeleted,
  1978  						Name: "Update",
  1979  						Fields: []*FieldDiff{
  1980  							{
  1981  								Type: DiffTypeDeleted,
  1982  								Name: "AutoPromote",
  1983  								Old:  "false",
  1984  								New:  "",
  1985  							},
  1986  							{
  1987  								Type: DiffTypeDeleted,
  1988  								Name: "AutoRevert",
  1989  								Old:  "true",
  1990  								New:  "",
  1991  							},
  1992  							{
  1993  								Type: DiffTypeDeleted,
  1994  								Name: "Canary",
  1995  								Old:  "0",
  1996  								New:  "",
  1997  							},
  1998  							{
  1999  								Type: DiffTypeDeleted,
  2000  								Name: "HealthyDeadline",
  2001  								Old:  "0",
  2002  								New:  "",
  2003  							},
  2004  							{
  2005  								Type: DiffTypeDeleted,
  2006  								Name: "MaxParallel",
  2007  								Old:  "0",
  2008  								New:  "",
  2009  							},
  2010  							{
  2011  								Type: DiffTypeDeleted,
  2012  								Name: "MinHealthyTime",
  2013  								Old:  "0",
  2014  								New:  "",
  2015  							},
  2016  							{
  2017  								Type: DiffTypeDeleted,
  2018  								Name: "ProgressDeadline",
  2019  								Old:  "0",
  2020  								New:  "",
  2021  							},
  2022  						},
  2023  					},
  2024  				},
  2025  			},
  2026  		},
  2027  		{
  2028  			// Update strategy added
  2029  			Old: &TaskGroup{},
  2030  			New: &TaskGroup{
  2031  				Update: &UpdateStrategy{
  2032  					AutoRevert: true,
  2033  				},
  2034  			},
  2035  			Expected: &TaskGroupDiff{
  2036  				Type: DiffTypeEdited,
  2037  				Objects: []*ObjectDiff{
  2038  					{
  2039  						Type: DiffTypeAdded,
  2040  						Name: "Update",
  2041  						Fields: []*FieldDiff{
  2042  							{
  2043  								Type: DiffTypeAdded,
  2044  								Name: "AutoPromote",
  2045  								Old:  "",
  2046  								New:  "false",
  2047  							},
  2048  							{
  2049  								Type: DiffTypeAdded,
  2050  								Name: "AutoRevert",
  2051  								Old:  "",
  2052  								New:  "true",
  2053  							},
  2054  							{
  2055  								Type: DiffTypeAdded,
  2056  								Name: "Canary",
  2057  								Old:  "",
  2058  								New:  "0",
  2059  							},
  2060  							{
  2061  								Type: DiffTypeAdded,
  2062  								Name: "HealthyDeadline",
  2063  								Old:  "",
  2064  								New:  "0",
  2065  							},
  2066  							{
  2067  								Type: DiffTypeAdded,
  2068  								Name: "MaxParallel",
  2069  								Old:  "",
  2070  								New:  "0",
  2071  							},
  2072  							{
  2073  								Type: DiffTypeAdded,
  2074  								Name: "MinHealthyTime",
  2075  								Old:  "",
  2076  								New:  "0",
  2077  							},
  2078  							{
  2079  								Type: DiffTypeAdded,
  2080  								Name: "ProgressDeadline",
  2081  								Old:  "",
  2082  								New:  "0",
  2083  							},
  2084  						},
  2085  					},
  2086  				},
  2087  			},
  2088  		},
  2089  		{
  2090  			// Update strategy edited
  2091  			Old: &TaskGroup{
  2092  				Update: &UpdateStrategy{
  2093  					MaxParallel:      5,
  2094  					HealthCheck:      "foo",
  2095  					MinHealthyTime:   1 * time.Second,
  2096  					HealthyDeadline:  30 * time.Second,
  2097  					ProgressDeadline: 29 * time.Second,
  2098  					AutoRevert:       true,
  2099  					AutoPromote:      true,
  2100  					Canary:           2,
  2101  				},
  2102  			},
  2103  			New: &TaskGroup{
  2104  				Update: &UpdateStrategy{
  2105  					MaxParallel:      7,
  2106  					HealthCheck:      "bar",
  2107  					MinHealthyTime:   2 * time.Second,
  2108  					HealthyDeadline:  31 * time.Second,
  2109  					ProgressDeadline: 32 * time.Second,
  2110  					AutoRevert:       false,
  2111  					AutoPromote:      false,
  2112  					Canary:           1,
  2113  				},
  2114  			},
  2115  			Expected: &TaskGroupDiff{
  2116  				Type: DiffTypeEdited,
  2117  				Objects: []*ObjectDiff{
  2118  					{
  2119  						Type: DiffTypeEdited,
  2120  						Name: "Update",
  2121  						Fields: []*FieldDiff{
  2122  							{
  2123  								Type: DiffTypeEdited,
  2124  								Name: "AutoPromote",
  2125  								Old:  "true",
  2126  								New:  "false",
  2127  							},
  2128  							{
  2129  								Type: DiffTypeEdited,
  2130  								Name: "AutoRevert",
  2131  								Old:  "true",
  2132  								New:  "false",
  2133  							},
  2134  							{
  2135  								Type: DiffTypeEdited,
  2136  								Name: "Canary",
  2137  								Old:  "2",
  2138  								New:  "1",
  2139  							},
  2140  							{
  2141  								Type: DiffTypeEdited,
  2142  								Name: "HealthCheck",
  2143  								Old:  "foo",
  2144  								New:  "bar",
  2145  							},
  2146  							{
  2147  								Type: DiffTypeEdited,
  2148  								Name: "HealthyDeadline",
  2149  								Old:  "30000000000",
  2150  								New:  "31000000000",
  2151  							},
  2152  							{
  2153  								Type: DiffTypeEdited,
  2154  								Name: "MaxParallel",
  2155  								Old:  "5",
  2156  								New:  "7",
  2157  							},
  2158  							{
  2159  								Type: DiffTypeEdited,
  2160  								Name: "MinHealthyTime",
  2161  								Old:  "1000000000",
  2162  								New:  "2000000000",
  2163  							},
  2164  							{
  2165  								Type: DiffTypeEdited,
  2166  								Name: "ProgressDeadline",
  2167  								Old:  "29000000000",
  2168  								New:  "32000000000",
  2169  							},
  2170  						},
  2171  					},
  2172  				},
  2173  			},
  2174  		},
  2175  		{
  2176  			// Update strategy edited with context
  2177  			Contextual: true,
  2178  			Old: &TaskGroup{
  2179  				Update: &UpdateStrategy{
  2180  					MaxParallel:      5,
  2181  					HealthCheck:      "foo",
  2182  					MinHealthyTime:   1 * time.Second,
  2183  					HealthyDeadline:  30 * time.Second,
  2184  					ProgressDeadline: 30 * time.Second,
  2185  					AutoRevert:       true,
  2186  					AutoPromote:      true,
  2187  					Canary:           2,
  2188  				},
  2189  			},
  2190  			New: &TaskGroup{
  2191  				Update: &UpdateStrategy{
  2192  					MaxParallel:      7,
  2193  					HealthCheck:      "foo",
  2194  					MinHealthyTime:   1 * time.Second,
  2195  					HealthyDeadline:  30 * time.Second,
  2196  					ProgressDeadline: 30 * time.Second,
  2197  					AutoRevert:       true,
  2198  					AutoPromote:      true,
  2199  					Canary:           2,
  2200  				},
  2201  			},
  2202  			Expected: &TaskGroupDiff{
  2203  				Type: DiffTypeEdited,
  2204  				Objects: []*ObjectDiff{
  2205  					{
  2206  						Type: DiffTypeEdited,
  2207  						Name: "Update",
  2208  						Fields: []*FieldDiff{
  2209  							{
  2210  								Type: DiffTypeNone,
  2211  								Name: "AutoPromote",
  2212  								Old:  "true",
  2213  								New:  "true",
  2214  							},
  2215  							{
  2216  								Type: DiffTypeNone,
  2217  								Name: "AutoRevert",
  2218  								Old:  "true",
  2219  								New:  "true",
  2220  							},
  2221  							{
  2222  								Type: DiffTypeNone,
  2223  								Name: "Canary",
  2224  								Old:  "2",
  2225  								New:  "2",
  2226  							},
  2227  							{
  2228  								Type: DiffTypeNone,
  2229  								Name: "HealthCheck",
  2230  								Old:  "foo",
  2231  								New:  "foo",
  2232  							},
  2233  							{
  2234  								Type: DiffTypeNone,
  2235  								Name: "HealthyDeadline",
  2236  								Old:  "30000000000",
  2237  								New:  "30000000000",
  2238  							},
  2239  							{
  2240  								Type: DiffTypeEdited,
  2241  								Name: "MaxParallel",
  2242  								Old:  "5",
  2243  								New:  "7",
  2244  							},
  2245  							{
  2246  								Type: DiffTypeNone,
  2247  								Name: "MinHealthyTime",
  2248  								Old:  "1000000000",
  2249  								New:  "1000000000",
  2250  							},
  2251  							{
  2252  								Type: DiffTypeNone,
  2253  								Name: "ProgressDeadline",
  2254  								Old:  "30000000000",
  2255  								New:  "30000000000",
  2256  							},
  2257  						},
  2258  					},
  2259  				},
  2260  			},
  2261  		},
  2262  		{
  2263  			// EphemeralDisk added
  2264  			Old: &TaskGroup{},
  2265  			New: &TaskGroup{
  2266  				EphemeralDisk: &EphemeralDisk{
  2267  					Migrate: true,
  2268  					Sticky:  true,
  2269  					SizeMB:  100,
  2270  				},
  2271  			},
  2272  			Expected: &TaskGroupDiff{
  2273  				Type: DiffTypeEdited,
  2274  				Objects: []*ObjectDiff{
  2275  					{
  2276  						Type: DiffTypeAdded,
  2277  						Name: "EphemeralDisk",
  2278  						Fields: []*FieldDiff{
  2279  							{
  2280  								Type: DiffTypeAdded,
  2281  								Name: "Migrate",
  2282  								Old:  "",
  2283  								New:  "true",
  2284  							},
  2285  							{
  2286  								Type: DiffTypeAdded,
  2287  								Name: "SizeMB",
  2288  								Old:  "",
  2289  								New:  "100",
  2290  							},
  2291  							{
  2292  								Type: DiffTypeAdded,
  2293  								Name: "Sticky",
  2294  								Old:  "",
  2295  								New:  "true",
  2296  							},
  2297  						},
  2298  					},
  2299  				},
  2300  			},
  2301  		},
  2302  		{
  2303  			// EphemeralDisk deleted
  2304  			Old: &TaskGroup{
  2305  				EphemeralDisk: &EphemeralDisk{
  2306  					Migrate: true,
  2307  					Sticky:  true,
  2308  					SizeMB:  100,
  2309  				},
  2310  			},
  2311  			New: &TaskGroup{},
  2312  			Expected: &TaskGroupDiff{
  2313  				Type: DiffTypeEdited,
  2314  				Objects: []*ObjectDiff{
  2315  					{
  2316  						Type: DiffTypeDeleted,
  2317  						Name: "EphemeralDisk",
  2318  						Fields: []*FieldDiff{
  2319  							{
  2320  								Type: DiffTypeDeleted,
  2321  								Name: "Migrate",
  2322  								Old:  "true",
  2323  								New:  "",
  2324  							},
  2325  							{
  2326  								Type: DiffTypeDeleted,
  2327  								Name: "SizeMB",
  2328  								Old:  "100",
  2329  								New:  "",
  2330  							},
  2331  							{
  2332  								Type: DiffTypeDeleted,
  2333  								Name: "Sticky",
  2334  								Old:  "true",
  2335  								New:  "",
  2336  							},
  2337  						},
  2338  					},
  2339  				},
  2340  			},
  2341  		},
  2342  		{
  2343  			// EphemeralDisk edited
  2344  			Old: &TaskGroup{
  2345  				EphemeralDisk: &EphemeralDisk{
  2346  					Migrate: true,
  2347  					Sticky:  true,
  2348  					SizeMB:  150,
  2349  				},
  2350  			},
  2351  			New: &TaskGroup{
  2352  				EphemeralDisk: &EphemeralDisk{
  2353  					Migrate: false,
  2354  					Sticky:  false,
  2355  					SizeMB:  90,
  2356  				},
  2357  			},
  2358  			Expected: &TaskGroupDiff{
  2359  				Type: DiffTypeEdited,
  2360  				Objects: []*ObjectDiff{
  2361  					{
  2362  						Type: DiffTypeEdited,
  2363  						Name: "EphemeralDisk",
  2364  						Fields: []*FieldDiff{
  2365  							{
  2366  								Type: DiffTypeEdited,
  2367  								Name: "Migrate",
  2368  								Old:  "true",
  2369  								New:  "false",
  2370  							},
  2371  							{
  2372  								Type: DiffTypeEdited,
  2373  								Name: "SizeMB",
  2374  								Old:  "150",
  2375  								New:  "90",
  2376  							},
  2377  
  2378  							{
  2379  								Type: DiffTypeEdited,
  2380  								Name: "Sticky",
  2381  								Old:  "true",
  2382  								New:  "false",
  2383  							},
  2384  						},
  2385  					},
  2386  				},
  2387  			},
  2388  		},
  2389  		{
  2390  			// EphemeralDisk edited with context
  2391  			Contextual: true,
  2392  			Old: &TaskGroup{
  2393  				EphemeralDisk: &EphemeralDisk{
  2394  					Migrate: false,
  2395  					Sticky:  false,
  2396  					SizeMB:  100,
  2397  				},
  2398  			},
  2399  			New: &TaskGroup{
  2400  				EphemeralDisk: &EphemeralDisk{
  2401  					Migrate: true,
  2402  					Sticky:  true,
  2403  					SizeMB:  90,
  2404  				},
  2405  			},
  2406  			Expected: &TaskGroupDiff{
  2407  				Type: DiffTypeEdited,
  2408  				Objects: []*ObjectDiff{
  2409  					{
  2410  						Type: DiffTypeEdited,
  2411  						Name: "EphemeralDisk",
  2412  						Fields: []*FieldDiff{
  2413  							{
  2414  								Type: DiffTypeEdited,
  2415  								Name: "Migrate",
  2416  								Old:  "false",
  2417  								New:  "true",
  2418  							},
  2419  							{
  2420  								Type: DiffTypeEdited,
  2421  								Name: "SizeMB",
  2422  								Old:  "100",
  2423  								New:  "90",
  2424  							},
  2425  							{
  2426  								Type: DiffTypeEdited,
  2427  								Name: "Sticky",
  2428  								Old:  "false",
  2429  								New:  "true",
  2430  							},
  2431  						},
  2432  					},
  2433  				},
  2434  			},
  2435  		},
  2436  
  2437  		{
  2438  			// TaskGroup Services edited
  2439  			Contextual: true,
  2440  			Old: &TaskGroup{
  2441  				Services: []*Service{
  2442  					{
  2443  						Name: "foo",
  2444  						Checks: []*ServiceCheck{
  2445  							{
  2446  								Name:     "foo",
  2447  								Type:     "http",
  2448  								Command:  "foo",
  2449  								Args:     []string{"foo"},
  2450  								Path:     "foo",
  2451  								Protocol: "http",
  2452  								Interval: 1 * time.Second,
  2453  								Timeout:  1 * time.Second,
  2454  							},
  2455  						},
  2456  						Connect: &ConsulConnect{
  2457  							SidecarTask: &SidecarTask{
  2458  								Name:   "sidecar",
  2459  								Driver: "docker",
  2460  								Env: map[string]string{
  2461  									"FOO": "BAR",
  2462  								},
  2463  								Config: map[string]interface{}{
  2464  									"foo": "baz",
  2465  								},
  2466  							},
  2467  						},
  2468  					},
  2469  				},
  2470  			},
  2471  
  2472  			New: &TaskGroup{
  2473  				Services: []*Service{
  2474  					{
  2475  						Name: "foo",
  2476  						Checks: []*ServiceCheck{
  2477  							{
  2478  								Name:     "foo",
  2479  								Type:     "tcp",
  2480  								Command:  "bar",
  2481  								Path:     "bar",
  2482  								Protocol: "tcp",
  2483  								Interval: 2 * time.Second,
  2484  								Timeout:  2 * time.Second,
  2485  								Header: map[string][]string{
  2486  									"Foo": {"baz"},
  2487  								},
  2488  							},
  2489  						},
  2490  						Connect: &ConsulConnect{
  2491  							SidecarService: &ConsulSidecarService{
  2492  								Port: "http",
  2493  								Proxy: &ConsulProxy{
  2494  									LocalServiceAddress: "127.0.0.1",
  2495  									LocalServicePort:    8080,
  2496  									Upstreams: []ConsulUpstream{
  2497  										{
  2498  											DestinationName: "foo",
  2499  											LocalBindPort:   8000,
  2500  										},
  2501  									},
  2502  									Config: map[string]interface{}{
  2503  										"foo": "qux",
  2504  									},
  2505  								},
  2506  							},
  2507  						},
  2508  					},
  2509  				},
  2510  			},
  2511  
  2512  			Expected: &TaskGroupDiff{
  2513  				Type: DiffTypeEdited,
  2514  				Objects: []*ObjectDiff{
  2515  					{
  2516  						Type: DiffTypeEdited,
  2517  						Name: "Service",
  2518  						Fields: []*FieldDiff{
  2519  							{
  2520  								Type: DiffTypeNone,
  2521  								Name: "AddressMode",
  2522  								Old:  "",
  2523  								New:  "",
  2524  							},
  2525  							{
  2526  								Type: DiffTypeNone,
  2527  								Name: "Name",
  2528  								Old:  "foo",
  2529  								New:  "foo",
  2530  							},
  2531  							{
  2532  								Type: DiffTypeNone,
  2533  								Name: "PortLabel",
  2534  								Old:  "",
  2535  								New:  "",
  2536  							},
  2537  						},
  2538  						Objects: []*ObjectDiff{
  2539  
  2540  							{
  2541  								Type: DiffTypeEdited,
  2542  								Name: "Check",
  2543  								Fields: []*FieldDiff{
  2544  									{
  2545  										Type: DiffTypeNone,
  2546  										Name: "AddressMode",
  2547  										Old:  "",
  2548  										New:  "",
  2549  									},
  2550  									{
  2551  										Type: DiffTypeEdited,
  2552  										Name: "Command",
  2553  										Old:  "foo",
  2554  										New:  "bar",
  2555  									},
  2556  									{
  2557  										Type: DiffTypeNone,
  2558  										Name: "GRPCService",
  2559  										Old:  "",
  2560  										New:  "",
  2561  									},
  2562  									{
  2563  										Type: DiffTypeNone,
  2564  										Name: "GRPCUseTLS",
  2565  										Old:  "false",
  2566  										New:  "false",
  2567  									},
  2568  									{
  2569  										Type: DiffTypeNone,
  2570  										Name: "InitialStatus",
  2571  										Old:  "",
  2572  										New:  "",
  2573  									},
  2574  									{
  2575  										Type: DiffTypeEdited,
  2576  										Name: "Interval",
  2577  										Old:  "1000000000",
  2578  										New:  "2000000000",
  2579  									},
  2580  									{
  2581  										Type: DiffTypeNone,
  2582  										Name: "Method",
  2583  										Old:  "",
  2584  										New:  "",
  2585  									},
  2586  									{
  2587  										Type: DiffTypeNone,
  2588  										Name: "Name",
  2589  										Old:  "foo",
  2590  										New:  "foo",
  2591  									},
  2592  									{
  2593  										Type: DiffTypeEdited,
  2594  										Name: "Path",
  2595  										Old:  "foo",
  2596  										New:  "bar",
  2597  									},
  2598  									{
  2599  										Type: DiffTypeNone,
  2600  										Name: "PortLabel",
  2601  										Old:  "",
  2602  										New:  "",
  2603  									},
  2604  									{
  2605  										Type: DiffTypeEdited,
  2606  										Name: "Protocol",
  2607  										Old:  "http",
  2608  										New:  "tcp",
  2609  									},
  2610  									{
  2611  										Type: DiffTypeNone,
  2612  										Name: "TLSSkipVerify",
  2613  										Old:  "false",
  2614  										New:  "false",
  2615  									},
  2616  									{
  2617  										Type: DiffTypeNone,
  2618  										Name: "TaskName",
  2619  										Old:  "",
  2620  										New:  "",
  2621  									},
  2622  									{
  2623  										Type: DiffTypeEdited,
  2624  										Name: "Timeout",
  2625  										Old:  "1000000000",
  2626  										New:  "2000000000",
  2627  									},
  2628  									{
  2629  										Type: DiffTypeEdited,
  2630  										Name: "Type",
  2631  										Old:  "http",
  2632  										New:  "tcp",
  2633  									},
  2634  								},
  2635  								Objects: []*ObjectDiff{
  2636  									{
  2637  										Type: DiffTypeAdded,
  2638  										Name: "Header",
  2639  										Fields: []*FieldDiff{
  2640  											{
  2641  												Type: DiffTypeAdded,
  2642  												Name: "Foo[0]",
  2643  												Old:  "",
  2644  												New:  "baz",
  2645  											},
  2646  										},
  2647  									},
  2648  								},
  2649  							},
  2650  
  2651  							{
  2652  								Type: DiffTypeEdited,
  2653  								Name: "ConsulConnect",
  2654  								Fields: []*FieldDiff{
  2655  									{
  2656  										Type: DiffTypeNone,
  2657  										Name: "Native",
  2658  										Old:  "false",
  2659  										New:  "false",
  2660  									},
  2661  								},
  2662  								Objects: []*ObjectDiff{
  2663  
  2664  									{
  2665  										Type: DiffTypeAdded,
  2666  										Name: "SidecarService",
  2667  										Fields: []*FieldDiff{
  2668  											{
  2669  												Type: DiffTypeAdded,
  2670  												Name: "Port",
  2671  												Old:  "",
  2672  												New:  "http",
  2673  											},
  2674  										},
  2675  										Objects: []*ObjectDiff{
  2676  											{
  2677  												Type: DiffTypeAdded,
  2678  												Name: "ConsulProxy",
  2679  												Fields: []*FieldDiff{
  2680  													{
  2681  														Type: DiffTypeAdded,
  2682  														Name: "LocalServiceAddress",
  2683  														Old:  "",
  2684  														New:  "127.0.0.1",
  2685  													}, {
  2686  														Type: DiffTypeAdded,
  2687  														Name: "LocalServicePort",
  2688  														Old:  "",
  2689  														New:  "8080",
  2690  													},
  2691  												},
  2692  												Objects: []*ObjectDiff{
  2693  													{
  2694  														Type: DiffTypeAdded,
  2695  														Name: "ConsulUpstreams",
  2696  														Fields: []*FieldDiff{
  2697  															{
  2698  																Type: DiffTypeAdded,
  2699  																Name: "DestinationName",
  2700  																Old:  "",
  2701  																New:  "foo",
  2702  															},
  2703  															{
  2704  																Type: DiffTypeAdded,
  2705  																Name: "LocalBindPort",
  2706  																Old:  "",
  2707  																New:  "8000",
  2708  															},
  2709  														},
  2710  													},
  2711  													{
  2712  														Type: DiffTypeAdded,
  2713  														Name: "Config",
  2714  														Fields: []*FieldDiff{
  2715  															{
  2716  																Type: DiffTypeAdded,
  2717  																Name: "foo",
  2718  																Old:  "",
  2719  																New:  "qux",
  2720  															},
  2721  														},
  2722  													},
  2723  												},
  2724  											},
  2725  										},
  2726  									},
  2727  
  2728  									{
  2729  										Type: DiffTypeDeleted,
  2730  										Name: "SidecarTask",
  2731  										Fields: []*FieldDiff{
  2732  											{
  2733  												Type: DiffTypeDeleted,
  2734  												Name: "Driver",
  2735  												Old:  "docker",
  2736  												New:  "",
  2737  											},
  2738  											{
  2739  												Type: DiffTypeDeleted,
  2740  												Name: "Env[FOO]",
  2741  												Old:  "BAR",
  2742  												New:  "",
  2743  											},
  2744  											{
  2745  												Type: DiffTypeDeleted,
  2746  												Name: "Name",
  2747  												Old:  "sidecar",
  2748  												New:  "",
  2749  											},
  2750  										},
  2751  										Objects: []*ObjectDiff{
  2752  											{
  2753  												Type: DiffTypeDeleted,
  2754  												Name: "Config",
  2755  												Fields: []*FieldDiff{
  2756  													{
  2757  														Type: DiffTypeDeleted,
  2758  														Name: "foo",
  2759  														Old:  "baz",
  2760  														New:  "",
  2761  													},
  2762  												},
  2763  											},
  2764  										},
  2765  									},
  2766  								},
  2767  							},
  2768  						},
  2769  					},
  2770  				},
  2771  			},
  2772  		},
  2773  		{
  2774  			// TaskGroup Networks edited
  2775  			Contextual: true,
  2776  			Old: &TaskGroup{
  2777  				Networks: Networks{
  2778  					{
  2779  						Device: "foo",
  2780  						CIDR:   "foo",
  2781  						IP:     "foo",
  2782  						MBits:  100,
  2783  						ReservedPorts: []Port{
  2784  							{
  2785  								Label: "foo",
  2786  								Value: 80,
  2787  							},
  2788  						},
  2789  					},
  2790  				},
  2791  			},
  2792  			New: &TaskGroup{
  2793  				Networks: Networks{
  2794  					{
  2795  						Device: "bar",
  2796  						CIDR:   "bar",
  2797  						IP:     "bar",
  2798  						MBits:  200,
  2799  						DynamicPorts: []Port{
  2800  							{
  2801  								Label: "bar",
  2802  								To:    8081,
  2803  							},
  2804  						},
  2805  					},
  2806  				},
  2807  			},
  2808  			Expected: &TaskGroupDiff{
  2809  				Type: DiffTypeEdited,
  2810  				Objects: []*ObjectDiff{
  2811  					{
  2812  						Type: DiffTypeAdded,
  2813  						Name: "Network",
  2814  						Fields: []*FieldDiff{
  2815  							{
  2816  								Type: DiffTypeAdded,
  2817  								Name: "MBits",
  2818  								Old:  "",
  2819  								New:  "200",
  2820  							},
  2821  							{
  2822  								Type: DiffTypeNone,
  2823  								Name: "Mode",
  2824  								Old:  "",
  2825  								New:  "",
  2826  							},
  2827  						},
  2828  						Objects: []*ObjectDiff{
  2829  							{
  2830  								Type: DiffTypeAdded,
  2831  								Name: "Dynamic Port",
  2832  								Fields: []*FieldDiff{
  2833  									{
  2834  										Type: DiffTypeAdded,
  2835  										Name: "Label",
  2836  										Old:  "",
  2837  										New:  "bar",
  2838  									},
  2839  									{
  2840  										Type: DiffTypeAdded,
  2841  										Name: "To",
  2842  										Old:  "",
  2843  										New:  "8081",
  2844  									},
  2845  								},
  2846  							},
  2847  						},
  2848  					},
  2849  					{
  2850  						Type: DiffTypeDeleted,
  2851  						Name: "Network",
  2852  						Fields: []*FieldDiff{
  2853  							{
  2854  								Type: DiffTypeDeleted,
  2855  								Name: "MBits",
  2856  								Old:  "100",
  2857  								New:  "",
  2858  							},
  2859  							{
  2860  								Type: DiffTypeNone,
  2861  								Name: "Mode",
  2862  								Old:  "",
  2863  								New:  "",
  2864  							},
  2865  						},
  2866  						Objects: []*ObjectDiff{
  2867  							{
  2868  								Type: DiffTypeDeleted,
  2869  								Name: "Static Port",
  2870  								Fields: []*FieldDiff{
  2871  									{
  2872  										Type: DiffTypeDeleted,
  2873  										Name: "Label",
  2874  										Old:  "foo",
  2875  										New:  "",
  2876  									},
  2877  									{
  2878  										Type: DiffTypeDeleted,
  2879  										Name: "To",
  2880  										Old:  "0",
  2881  										New:  "",
  2882  									},
  2883  									{
  2884  										Type: DiffTypeDeleted,
  2885  										Name: "Value",
  2886  										Old:  "80",
  2887  										New:  "",
  2888  									},
  2889  								},
  2890  							},
  2891  						},
  2892  					},
  2893  				},
  2894  			},
  2895  		},
  2896  		{
  2897  			// Tasks edited
  2898  			Old: &TaskGroup{
  2899  				Tasks: []*Task{
  2900  					{
  2901  						Name:   "foo",
  2902  						Driver: "docker",
  2903  					},
  2904  					{
  2905  						Name:   "bar",
  2906  						Driver: "docker",
  2907  					},
  2908  					{
  2909  						Name:          "baz",
  2910  						ShutdownDelay: 1 * time.Second,
  2911  					},
  2912  				},
  2913  			},
  2914  			New: &TaskGroup{
  2915  				Tasks: []*Task{
  2916  					{
  2917  						Name:   "bar",
  2918  						Driver: "docker",
  2919  					},
  2920  					{
  2921  						Name:   "bam",
  2922  						Driver: "docker",
  2923  					},
  2924  					{
  2925  						Name:          "baz",
  2926  						ShutdownDelay: 2 * time.Second,
  2927  					},
  2928  				},
  2929  			},
  2930  			Expected: &TaskGroupDiff{
  2931  				Type: DiffTypeEdited,
  2932  				Tasks: []*TaskDiff{
  2933  					{
  2934  						Type: DiffTypeAdded,
  2935  						Name: "bam",
  2936  						Fields: []*FieldDiff{
  2937  							{
  2938  								Type: DiffTypeAdded,
  2939  								Name: "Driver",
  2940  								Old:  "",
  2941  								New:  "docker",
  2942  							},
  2943  							{
  2944  								Type: DiffTypeAdded,
  2945  								Name: "KillTimeout",
  2946  								Old:  "",
  2947  								New:  "0",
  2948  							},
  2949  							{
  2950  								Type: DiffTypeAdded,
  2951  								Name: "Leader",
  2952  								Old:  "",
  2953  								New:  "false",
  2954  							},
  2955  							{
  2956  								Type: DiffTypeAdded,
  2957  								Name: "ShutdownDelay",
  2958  								Old:  "",
  2959  								New:  "0",
  2960  							},
  2961  						},
  2962  					},
  2963  					{
  2964  						Type: DiffTypeNone,
  2965  						Name: "bar",
  2966  					},
  2967  					{
  2968  						Type: DiffTypeEdited,
  2969  						Name: "baz",
  2970  						Fields: []*FieldDiff{
  2971  							{
  2972  								Type: DiffTypeEdited,
  2973  								Name: "ShutdownDelay",
  2974  								Old:  "1000000000",
  2975  								New:  "2000000000",
  2976  							},
  2977  						},
  2978  					},
  2979  					{
  2980  						Type: DiffTypeDeleted,
  2981  						Name: "foo",
  2982  						Fields: []*FieldDiff{
  2983  							{
  2984  								Type: DiffTypeDeleted,
  2985  								Name: "Driver",
  2986  								Old:  "docker",
  2987  								New:  "",
  2988  							},
  2989  							{
  2990  								Type: DiffTypeDeleted,
  2991  								Name: "KillTimeout",
  2992  								Old:  "0",
  2993  								New:  "",
  2994  							},
  2995  							{
  2996  								Type: DiffTypeDeleted,
  2997  								Name: "Leader",
  2998  								Old:  "false",
  2999  								New:  "",
  3000  							},
  3001  							{
  3002  								Type: DiffTypeDeleted,
  3003  								Name: "ShutdownDelay",
  3004  								Old:  "0",
  3005  								New:  "",
  3006  							},
  3007  						},
  3008  					},
  3009  				},
  3010  			},
  3011  		},
  3012  	}
  3013  
  3014  	for i, c := range cases {
  3015  		actual, err := c.Old.Diff(c.New, c.Contextual)
  3016  		if c.Error && err == nil {
  3017  			t.Fatalf("case %d: expected errored", i+1)
  3018  		} else if err != nil {
  3019  			if !c.Error {
  3020  				t.Fatalf("case %d: errored %#v", i+1, err)
  3021  			} else {
  3022  				continue
  3023  			}
  3024  		}
  3025  
  3026  		if !reflect.DeepEqual(actual, c.Expected) {
  3027  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  3028  				i+1, actual, c.Expected)
  3029  		}
  3030  	}
  3031  }
  3032  
  3033  func TestTaskDiff(t *testing.T) {
  3034  	cases := []struct {
  3035  		Name       string
  3036  		Old, New   *Task
  3037  		Expected   *TaskDiff
  3038  		Error      bool
  3039  		Contextual bool
  3040  	}{
  3041  		{
  3042  			Name: "Empty",
  3043  			Old:  nil,
  3044  			New:  nil,
  3045  			Expected: &TaskDiff{
  3046  				Type: DiffTypeNone,
  3047  			},
  3048  		},
  3049  		{
  3050  			Name: "Primitive only that has different names",
  3051  			Old: &Task{
  3052  				Name: "foo",
  3053  				Meta: map[string]string{
  3054  					"foo": "bar",
  3055  				},
  3056  			},
  3057  			New: &Task{
  3058  				Name: "bar",
  3059  				Meta: map[string]string{
  3060  					"foo": "bar",
  3061  				},
  3062  			},
  3063  			Error: true,
  3064  		},
  3065  		{
  3066  			Name: "Primitive only that is the same",
  3067  			Old: &Task{
  3068  				Name:   "foo",
  3069  				Driver: "exec",
  3070  				User:   "foo",
  3071  				Env: map[string]string{
  3072  					"FOO": "bar",
  3073  				},
  3074  				Meta: map[string]string{
  3075  					"foo": "bar",
  3076  				},
  3077  				KillTimeout: 1 * time.Second,
  3078  				Leader:      true,
  3079  			},
  3080  			New: &Task{
  3081  				Name:   "foo",
  3082  				Driver: "exec",
  3083  				User:   "foo",
  3084  				Env: map[string]string{
  3085  					"FOO": "bar",
  3086  				},
  3087  				Meta: map[string]string{
  3088  					"foo": "bar",
  3089  				},
  3090  				KillTimeout: 1 * time.Second,
  3091  				Leader:      true,
  3092  			},
  3093  			Expected: &TaskDiff{
  3094  				Type: DiffTypeNone,
  3095  				Name: "foo",
  3096  			},
  3097  		},
  3098  		{
  3099  			Name: "Primitive only that has diffs",
  3100  			Old: &Task{
  3101  				Name:   "foo",
  3102  				Driver: "exec",
  3103  				User:   "foo",
  3104  				Env: map[string]string{
  3105  					"FOO": "bar",
  3106  				},
  3107  				Meta: map[string]string{
  3108  					"foo": "bar",
  3109  				},
  3110  				KillTimeout: 1 * time.Second,
  3111  				Leader:      true,
  3112  			},
  3113  			New: &Task{
  3114  				Name:   "foo",
  3115  				Driver: "docker",
  3116  				User:   "bar",
  3117  				Env: map[string]string{
  3118  					"FOO": "baz",
  3119  				},
  3120  				Meta: map[string]string{
  3121  					"foo": "baz",
  3122  				},
  3123  				KillTimeout: 2 * time.Second,
  3124  				Leader:      false,
  3125  			},
  3126  			Expected: &TaskDiff{
  3127  				Type: DiffTypeEdited,
  3128  				Name: "foo",
  3129  				Fields: []*FieldDiff{
  3130  					{
  3131  						Type: DiffTypeEdited,
  3132  						Name: "Driver",
  3133  						Old:  "exec",
  3134  						New:  "docker",
  3135  					},
  3136  					{
  3137  						Type: DiffTypeEdited,
  3138  						Name: "Env[FOO]",
  3139  						Old:  "bar",
  3140  						New:  "baz",
  3141  					},
  3142  					{
  3143  						Type: DiffTypeEdited,
  3144  						Name: "KillTimeout",
  3145  						Old:  "1000000000",
  3146  						New:  "2000000000",
  3147  					},
  3148  					{
  3149  						Type: DiffTypeEdited,
  3150  						Name: "Leader",
  3151  						Old:  "true",
  3152  						New:  "false",
  3153  					},
  3154  					{
  3155  						Type: DiffTypeEdited,
  3156  						Name: "Meta[foo]",
  3157  						Old:  "bar",
  3158  						New:  "baz",
  3159  					},
  3160  					{
  3161  						Type: DiffTypeEdited,
  3162  						Name: "User",
  3163  						Old:  "foo",
  3164  						New:  "bar",
  3165  					},
  3166  				},
  3167  			},
  3168  		},
  3169  		{
  3170  			Name: "Map diff",
  3171  			Old: &Task{
  3172  				Meta: map[string]string{
  3173  					"foo": "foo",
  3174  					"bar": "bar",
  3175  				},
  3176  				Env: map[string]string{
  3177  					"foo": "foo",
  3178  					"bar": "bar",
  3179  				},
  3180  			},
  3181  			New: &Task{
  3182  				Meta: map[string]string{
  3183  					"bar": "bar",
  3184  					"baz": "baz",
  3185  				},
  3186  				Env: map[string]string{
  3187  					"bar": "bar",
  3188  					"baz": "baz",
  3189  				},
  3190  			},
  3191  			Expected: &TaskDiff{
  3192  				Type: DiffTypeEdited,
  3193  				Fields: []*FieldDiff{
  3194  					{
  3195  						Type: DiffTypeAdded,
  3196  						Name: "Env[baz]",
  3197  						Old:  "",
  3198  						New:  "baz",
  3199  					},
  3200  					{
  3201  						Type: DiffTypeDeleted,
  3202  						Name: "Env[foo]",
  3203  						Old:  "foo",
  3204  						New:  "",
  3205  					},
  3206  					{
  3207  						Type: DiffTypeAdded,
  3208  						Name: "Meta[baz]",
  3209  						Old:  "",
  3210  						New:  "baz",
  3211  					},
  3212  					{
  3213  						Type: DiffTypeDeleted,
  3214  						Name: "Meta[foo]",
  3215  						Old:  "foo",
  3216  						New:  "",
  3217  					},
  3218  				},
  3219  			},
  3220  		},
  3221  		{
  3222  			Name: "Constraints edited",
  3223  			Old: &Task{
  3224  				Constraints: []*Constraint{
  3225  					{
  3226  						LTarget: "foo",
  3227  						RTarget: "foo",
  3228  						Operand: "foo",
  3229  						str:     "foo",
  3230  					},
  3231  					{
  3232  						LTarget: "bar",
  3233  						RTarget: "bar",
  3234  						Operand: "bar",
  3235  						str:     "bar",
  3236  					},
  3237  				},
  3238  			},
  3239  			New: &Task{
  3240  				Constraints: []*Constraint{
  3241  					{
  3242  						LTarget: "foo",
  3243  						RTarget: "foo",
  3244  						Operand: "foo",
  3245  						str:     "foo",
  3246  					},
  3247  					{
  3248  						LTarget: "baz",
  3249  						RTarget: "baz",
  3250  						Operand: "baz",
  3251  						str:     "baz",
  3252  					},
  3253  				},
  3254  			},
  3255  			Expected: &TaskDiff{
  3256  				Type: DiffTypeEdited,
  3257  				Objects: []*ObjectDiff{
  3258  					{
  3259  						Type: DiffTypeAdded,
  3260  						Name: "Constraint",
  3261  						Fields: []*FieldDiff{
  3262  							{
  3263  								Type: DiffTypeAdded,
  3264  								Name: "LTarget",
  3265  								Old:  "",
  3266  								New:  "baz",
  3267  							},
  3268  							{
  3269  								Type: DiffTypeAdded,
  3270  								Name: "Operand",
  3271  								Old:  "",
  3272  								New:  "baz",
  3273  							},
  3274  							{
  3275  								Type: DiffTypeAdded,
  3276  								Name: "RTarget",
  3277  								Old:  "",
  3278  								New:  "baz",
  3279  							},
  3280  						},
  3281  					},
  3282  					{
  3283  						Type: DiffTypeDeleted,
  3284  						Name: "Constraint",
  3285  						Fields: []*FieldDiff{
  3286  							{
  3287  								Type: DiffTypeDeleted,
  3288  								Name: "LTarget",
  3289  								Old:  "bar",
  3290  								New:  "",
  3291  							},
  3292  							{
  3293  								Type: DiffTypeDeleted,
  3294  								Name: "Operand",
  3295  								Old:  "bar",
  3296  								New:  "",
  3297  							},
  3298  							{
  3299  								Type: DiffTypeDeleted,
  3300  								Name: "RTarget",
  3301  								Old:  "bar",
  3302  								New:  "",
  3303  							},
  3304  						},
  3305  					},
  3306  				},
  3307  			},
  3308  		},
  3309  		{
  3310  			Name: "Affinities edited",
  3311  			Old: &Task{
  3312  				Affinities: []*Affinity{
  3313  					{
  3314  						LTarget: "foo",
  3315  						RTarget: "foo",
  3316  						Operand: "foo",
  3317  						Weight:  20,
  3318  						str:     "foo",
  3319  					},
  3320  					{
  3321  						LTarget: "bar",
  3322  						RTarget: "bar",
  3323  						Operand: "bar",
  3324  						Weight:  20,
  3325  						str:     "bar",
  3326  					},
  3327  				},
  3328  			},
  3329  			New: &Task{
  3330  				Affinities: []*Affinity{
  3331  					{
  3332  						LTarget: "foo",
  3333  						RTarget: "foo",
  3334  						Operand: "foo",
  3335  						Weight:  20,
  3336  						str:     "foo",
  3337  					},
  3338  					{
  3339  						LTarget: "baz",
  3340  						RTarget: "baz",
  3341  						Operand: "baz",
  3342  						Weight:  20,
  3343  						str:     "baz",
  3344  					},
  3345  				},
  3346  			},
  3347  			Expected: &TaskDiff{
  3348  				Type: DiffTypeEdited,
  3349  				Objects: []*ObjectDiff{
  3350  					{
  3351  						Type: DiffTypeAdded,
  3352  						Name: "Affinity",
  3353  						Fields: []*FieldDiff{
  3354  							{
  3355  								Type: DiffTypeAdded,
  3356  								Name: "LTarget",
  3357  								Old:  "",
  3358  								New:  "baz",
  3359  							},
  3360  							{
  3361  								Type: DiffTypeAdded,
  3362  								Name: "Operand",
  3363  								Old:  "",
  3364  								New:  "baz",
  3365  							},
  3366  							{
  3367  								Type: DiffTypeAdded,
  3368  								Name: "RTarget",
  3369  								Old:  "",
  3370  								New:  "baz",
  3371  							},
  3372  							{
  3373  								Type: DiffTypeAdded,
  3374  								Name: "Weight",
  3375  								Old:  "",
  3376  								New:  "20",
  3377  							},
  3378  						},
  3379  					},
  3380  					{
  3381  						Type: DiffTypeDeleted,
  3382  						Name: "Affinity",
  3383  						Fields: []*FieldDiff{
  3384  							{
  3385  								Type: DiffTypeDeleted,
  3386  								Name: "LTarget",
  3387  								Old:  "bar",
  3388  								New:  "",
  3389  							},
  3390  							{
  3391  								Type: DiffTypeDeleted,
  3392  								Name: "Operand",
  3393  								Old:  "bar",
  3394  								New:  "",
  3395  							},
  3396  							{
  3397  								Type: DiffTypeDeleted,
  3398  								Name: "RTarget",
  3399  								Old:  "bar",
  3400  								New:  "",
  3401  							},
  3402  							{
  3403  								Type: DiffTypeDeleted,
  3404  								Name: "Weight",
  3405  								Old:  "20",
  3406  								New:  "",
  3407  							},
  3408  						},
  3409  					},
  3410  				},
  3411  			},
  3412  		},
  3413  		{
  3414  			Name: "LogConfig added",
  3415  			Old:  &Task{},
  3416  			New: &Task{
  3417  				LogConfig: &LogConfig{
  3418  					MaxFiles:      1,
  3419  					MaxFileSizeMB: 10,
  3420  					FileExtension: "txt",
  3421  				},
  3422  			},
  3423  			Expected: &TaskDiff{
  3424  				Type: DiffTypeEdited,
  3425  				Objects: []*ObjectDiff{
  3426  					{
  3427  						Type: DiffTypeAdded,
  3428  						Name: "LogConfig",
  3429  						Fields: []*FieldDiff{
  3430  							{
  3431  								Type: DiffTypeAdded,
  3432  								Name: "FileExtension",
  3433  								Old:  "",
  3434  								New:  "txt",
  3435  							},
  3436  							{
  3437  								Type: DiffTypeAdded,
  3438  								Name: "MaxFileSizeMB",
  3439  								Old:  "",
  3440  								New:  "10",
  3441  							},
  3442  							{
  3443  								Type: DiffTypeAdded,
  3444  								Name: "MaxFiles",
  3445  								Old:  "",
  3446  								New:  "1",
  3447  							},
  3448  						},
  3449  					},
  3450  				},
  3451  			},
  3452  		},
  3453  		{
  3454  			Name: "LogConfig deleted",
  3455  			Old: &Task{
  3456  				LogConfig: &LogConfig{
  3457  					MaxFiles:      1,
  3458  					MaxFileSizeMB: 10,
  3459  					FileExtension: "txt",
  3460  				},
  3461  			},
  3462  			New: &Task{},
  3463  			Expected: &TaskDiff{
  3464  				Type: DiffTypeEdited,
  3465  				Objects: []*ObjectDiff{
  3466  					{
  3467  						Type: DiffTypeDeleted,
  3468  						Name: "LogConfig",
  3469  						Fields: []*FieldDiff{
  3470  							{
  3471  								Type: DiffTypeDeleted,
  3472  								Name: "FileExtension",
  3473  								Old:  "txt",
  3474  								New:  "",
  3475  							},
  3476  							{
  3477  								Type: DiffTypeDeleted,
  3478  								Name: "MaxFileSizeMB",
  3479  								Old:  "10",
  3480  								New:  "",
  3481  							},
  3482  							{
  3483  								Type: DiffTypeDeleted,
  3484  								Name: "MaxFiles",
  3485  								Old:  "1",
  3486  								New:  "",
  3487  							},
  3488  						},
  3489  					},
  3490  				},
  3491  			},
  3492  		},
  3493  		{
  3494  			Name: "LogConfig edited",
  3495  			Old: &Task{
  3496  				LogConfig: &LogConfig{
  3497  					MaxFiles:      1,
  3498  					MaxFileSizeMB: 10,
  3499  					FileExtension: "log",
  3500  				},
  3501  			},
  3502  			New: &Task{
  3503  				LogConfig: &LogConfig{
  3504  					MaxFiles:      2,
  3505  					MaxFileSizeMB: 20,
  3506  					FileExtension: "txt",
  3507  				},
  3508  			},
  3509  			Expected: &TaskDiff{
  3510  				Type: DiffTypeEdited,
  3511  				Objects: []*ObjectDiff{
  3512  					{
  3513  						Type: DiffTypeEdited,
  3514  						Name: "LogConfig",
  3515  						Fields: []*FieldDiff{
  3516  							{
  3517  								Type: DiffTypeEdited,
  3518  								Name: "FileExtension",
  3519  								Old:  "log",
  3520  								New:  "txt",
  3521  							},
  3522  							{
  3523  								Type: DiffTypeEdited,
  3524  								Name: "MaxFileSizeMB",
  3525  								Old:  "10",
  3526  								New:  "20",
  3527  							},
  3528  							{
  3529  								Type: DiffTypeEdited,
  3530  								Name: "MaxFiles",
  3531  								Old:  "1",
  3532  								New:  "2",
  3533  							},
  3534  						},
  3535  					},
  3536  				},
  3537  			},
  3538  		},
  3539  		{
  3540  			Name:       "LogConfig edited with context",
  3541  			Contextual: true,
  3542  			Old: &Task{
  3543  				LogConfig: &LogConfig{
  3544  					MaxFiles:      1,
  3545  					MaxFileSizeMB: 10,
  3546  					FileExtension: "txt",
  3547  				},
  3548  			},
  3549  			New: &Task{
  3550  				LogConfig: &LogConfig{
  3551  					MaxFiles:      1,
  3552  					MaxFileSizeMB: 20,
  3553  					FileExtension: "txt",
  3554  				},
  3555  			},
  3556  			Expected: &TaskDiff{
  3557  				Type: DiffTypeEdited,
  3558  				Objects: []*ObjectDiff{
  3559  					{
  3560  						Type: DiffTypeEdited,
  3561  						Name: "LogConfig",
  3562  						Fields: []*FieldDiff{
  3563  							{
  3564  								Type: DiffTypeNone,
  3565  								Name: "FileExtension",
  3566  								Old:  "txt",
  3567  								New:  "txt",
  3568  							},
  3569  							{
  3570  								Type: DiffTypeEdited,
  3571  								Name: "MaxFileSizeMB",
  3572  								Old:  "10",
  3573  								New:  "20",
  3574  							},
  3575  							{
  3576  								Type: DiffTypeNone,
  3577  								Name: "MaxFiles",
  3578  								Old:  "1",
  3579  								New:  "1",
  3580  							},
  3581  						},
  3582  					},
  3583  				},
  3584  			},
  3585  		},
  3586  		{
  3587  			Name: "Artifacts edited",
  3588  			Old: &Task{
  3589  				Artifacts: []*TaskArtifact{
  3590  					{
  3591  						GetterSource: "foo",
  3592  						GetterOptions: map[string]string{
  3593  							"foo": "bar",
  3594  						},
  3595  						RelativeDest: "foo",
  3596  					},
  3597  					{
  3598  						GetterSource: "bar",
  3599  						GetterOptions: map[string]string{
  3600  							"bar": "baz",
  3601  						},
  3602  						GetterMode:   "dir",
  3603  						RelativeDest: "bar",
  3604  					},
  3605  				},
  3606  			},
  3607  			New: &Task{
  3608  				Artifacts: []*TaskArtifact{
  3609  					{
  3610  						GetterSource: "foo",
  3611  						GetterOptions: map[string]string{
  3612  							"foo": "bar",
  3613  						},
  3614  						RelativeDest: "foo",
  3615  					},
  3616  					{
  3617  						GetterSource: "bam",
  3618  						GetterOptions: map[string]string{
  3619  							"bam": "baz",
  3620  						},
  3621  						GetterMode:   "file",
  3622  						RelativeDest: "bam",
  3623  					},
  3624  				},
  3625  			},
  3626  			Expected: &TaskDiff{
  3627  				Type: DiffTypeEdited,
  3628  				Objects: []*ObjectDiff{
  3629  					{
  3630  						Type: DiffTypeAdded,
  3631  						Name: "Artifact",
  3632  						Fields: []*FieldDiff{
  3633  							{
  3634  								Type: DiffTypeAdded,
  3635  								Name: "GetterMode",
  3636  								Old:  "",
  3637  								New:  "file",
  3638  							},
  3639  							{
  3640  								Type: DiffTypeAdded,
  3641  								Name: "GetterOptions[bam]",
  3642  								Old:  "",
  3643  								New:  "baz",
  3644  							},
  3645  							{
  3646  								Type: DiffTypeAdded,
  3647  								Name: "GetterSource",
  3648  								Old:  "",
  3649  								New:  "bam",
  3650  							},
  3651  							{
  3652  								Type: DiffTypeAdded,
  3653  								Name: "RelativeDest",
  3654  								Old:  "",
  3655  								New:  "bam",
  3656  							},
  3657  						},
  3658  					},
  3659  					{
  3660  						Type: DiffTypeDeleted,
  3661  						Name: "Artifact",
  3662  						Fields: []*FieldDiff{
  3663  							{
  3664  								Type: DiffTypeDeleted,
  3665  								Name: "GetterMode",
  3666  								Old:  "dir",
  3667  								New:  "",
  3668  							},
  3669  							{
  3670  								Type: DiffTypeDeleted,
  3671  								Name: "GetterOptions[bar]",
  3672  								Old:  "baz",
  3673  								New:  "",
  3674  							},
  3675  							{
  3676  								Type: DiffTypeDeleted,
  3677  								Name: "GetterSource",
  3678  								Old:  "bar",
  3679  								New:  "",
  3680  							},
  3681  							{
  3682  								Type: DiffTypeDeleted,
  3683  								Name: "RelativeDest",
  3684  								Old:  "bar",
  3685  								New:  "",
  3686  							},
  3687  						},
  3688  					},
  3689  				},
  3690  			},
  3691  		},
  3692  		{
  3693  			Name: "Resources edited (no networks)",
  3694  			Old: &Task{
  3695  				Resources: &Resources{
  3696  					CPU:      100,
  3697  					MemoryMB: 100,
  3698  					DiskMB:   100,
  3699  				},
  3700  			},
  3701  			New: &Task{
  3702  				Resources: &Resources{
  3703  					CPU:      200,
  3704  					MemoryMB: 200,
  3705  					DiskMB:   200,
  3706  				},
  3707  			},
  3708  			Expected: &TaskDiff{
  3709  				Type: DiffTypeEdited,
  3710  				Objects: []*ObjectDiff{
  3711  					{
  3712  						Type: DiffTypeEdited,
  3713  						Name: "Resources",
  3714  						Fields: []*FieldDiff{
  3715  							{
  3716  								Type: DiffTypeEdited,
  3717  								Name: "CPU",
  3718  								Old:  "100",
  3719  								New:  "200",
  3720  							},
  3721  							{
  3722  								Type: DiffTypeEdited,
  3723  								Name: "DiskMB",
  3724  								Old:  "100",
  3725  								New:  "200",
  3726  							},
  3727  							{
  3728  								Type: DiffTypeEdited,
  3729  								Name: "MemoryMB",
  3730  								Old:  "100",
  3731  								New:  "200",
  3732  							},
  3733  						},
  3734  					},
  3735  				},
  3736  			},
  3737  		},
  3738  		{
  3739  			Name:       "Resources edited (no networks) with context",
  3740  			Contextual: true,
  3741  			Old: &Task{
  3742  				Resources: &Resources{
  3743  					CPU:      100,
  3744  					MemoryMB: 100,
  3745  					DiskMB:   100,
  3746  				},
  3747  			},
  3748  			New: &Task{
  3749  				Resources: &Resources{
  3750  					CPU:      200,
  3751  					MemoryMB: 100,
  3752  					DiskMB:   200,
  3753  				},
  3754  			},
  3755  			Expected: &TaskDiff{
  3756  				Type: DiffTypeEdited,
  3757  				Objects: []*ObjectDiff{
  3758  					{
  3759  						Type: DiffTypeEdited,
  3760  						Name: "Resources",
  3761  						Fields: []*FieldDiff{
  3762  							{
  3763  								Type: DiffTypeEdited,
  3764  								Name: "CPU",
  3765  								Old:  "100",
  3766  								New:  "200",
  3767  							},
  3768  							{
  3769  								Type: DiffTypeEdited,
  3770  								Name: "DiskMB",
  3771  								Old:  "100",
  3772  								New:  "200",
  3773  							},
  3774  							{
  3775  								Type: DiffTypeNone,
  3776  								Name: "IOPS",
  3777  								Old:  "0",
  3778  								New:  "0",
  3779  							},
  3780  							{
  3781  								Type: DiffTypeNone,
  3782  								Name: "MemoryMB",
  3783  								Old:  "100",
  3784  								New:  "100",
  3785  							},
  3786  						},
  3787  					},
  3788  				},
  3789  			},
  3790  		},
  3791  		{
  3792  			Name: "Network Resources edited",
  3793  			Old: &Task{
  3794  				Resources: &Resources{
  3795  					Networks: []*NetworkResource{
  3796  						{
  3797  							Device: "foo",
  3798  							CIDR:   "foo",
  3799  							IP:     "foo",
  3800  							MBits:  100,
  3801  							ReservedPorts: []Port{
  3802  								{
  3803  									Label: "foo",
  3804  									Value: 80,
  3805  								},
  3806  							},
  3807  							DynamicPorts: []Port{
  3808  								{
  3809  									Label: "bar",
  3810  									To:    8080,
  3811  								},
  3812  							},
  3813  						},
  3814  					},
  3815  				},
  3816  			},
  3817  			New: &Task{
  3818  				Resources: &Resources{
  3819  					Networks: []*NetworkResource{
  3820  						{
  3821  							Device: "bar",
  3822  							CIDR:   "bar",
  3823  							IP:     "bar",
  3824  							MBits:  200,
  3825  							ReservedPorts: []Port{
  3826  								{
  3827  									Label: "foo",
  3828  									Value: 81,
  3829  								},
  3830  							},
  3831  							DynamicPorts: []Port{
  3832  								{
  3833  									Label: "baz",
  3834  									To:    8081,
  3835  								},
  3836  							},
  3837  						},
  3838  					},
  3839  				},
  3840  			},
  3841  			Expected: &TaskDiff{
  3842  				Type: DiffTypeEdited,
  3843  				Objects: []*ObjectDiff{
  3844  					{
  3845  						Type: DiffTypeEdited,
  3846  						Name: "Resources",
  3847  						Objects: []*ObjectDiff{
  3848  							{
  3849  								Type: DiffTypeAdded,
  3850  								Name: "Network",
  3851  								Fields: []*FieldDiff{
  3852  									{
  3853  										Type: DiffTypeAdded,
  3854  										Name: "MBits",
  3855  										Old:  "",
  3856  										New:  "200",
  3857  									},
  3858  								},
  3859  								Objects: []*ObjectDiff{
  3860  									{
  3861  										Type: DiffTypeAdded,
  3862  										Name: "Static Port",
  3863  										Fields: []*FieldDiff{
  3864  											{
  3865  												Type: DiffTypeAdded,
  3866  												Name: "Label",
  3867  												Old:  "",
  3868  												New:  "foo",
  3869  											},
  3870  											{
  3871  												Type: DiffTypeAdded,
  3872  												Name: "To",
  3873  												Old:  "",
  3874  												New:  "0",
  3875  											},
  3876  											{
  3877  												Type: DiffTypeAdded,
  3878  												Name: "Value",
  3879  												Old:  "",
  3880  												New:  "81",
  3881  											},
  3882  										},
  3883  									},
  3884  									{
  3885  										Type: DiffTypeAdded,
  3886  										Name: "Dynamic Port",
  3887  										Fields: []*FieldDiff{
  3888  											{
  3889  												Type: DiffTypeAdded,
  3890  												Name: "Label",
  3891  												Old:  "",
  3892  												New:  "baz",
  3893  											},
  3894  											{
  3895  												Type: DiffTypeAdded,
  3896  												Name: "To",
  3897  												Old:  "",
  3898  												New:  "8081",
  3899  											},
  3900  										},
  3901  									},
  3902  								},
  3903  							},
  3904  							{
  3905  								Type: DiffTypeDeleted,
  3906  								Name: "Network",
  3907  								Fields: []*FieldDiff{
  3908  									{
  3909  										Type: DiffTypeDeleted,
  3910  										Name: "MBits",
  3911  										Old:  "100",
  3912  										New:  "",
  3913  									},
  3914  								},
  3915  								Objects: []*ObjectDiff{
  3916  									{
  3917  										Type: DiffTypeDeleted,
  3918  										Name: "Static Port",
  3919  										Fields: []*FieldDiff{
  3920  											{
  3921  												Type: DiffTypeDeleted,
  3922  												Name: "Label",
  3923  												Old:  "foo",
  3924  												New:  "",
  3925  											},
  3926  											{
  3927  												Type: DiffTypeDeleted,
  3928  												Name: "To",
  3929  												Old:  "0",
  3930  												New:  "",
  3931  											},
  3932  											{
  3933  												Type: DiffTypeDeleted,
  3934  												Name: "Value",
  3935  												Old:  "80",
  3936  												New:  "",
  3937  											},
  3938  										},
  3939  									},
  3940  									{
  3941  										Type: DiffTypeDeleted,
  3942  										Name: "Dynamic Port",
  3943  										Fields: []*FieldDiff{
  3944  											{
  3945  												Type: DiffTypeDeleted,
  3946  												Name: "Label",
  3947  												Old:  "bar",
  3948  												New:  "",
  3949  											},
  3950  											{
  3951  												Type: DiffTypeDeleted,
  3952  												Name: "To",
  3953  												Old:  "8080",
  3954  												New:  "",
  3955  											},
  3956  										},
  3957  									},
  3958  								},
  3959  							},
  3960  						},
  3961  					},
  3962  				},
  3963  			},
  3964  		},
  3965  		{
  3966  			Name: "Device Resources edited",
  3967  			Old: &Task{
  3968  				Resources: &Resources{
  3969  					Devices: []*RequestedDevice{
  3970  						{
  3971  							Name:  "foo",
  3972  							Count: 2,
  3973  						},
  3974  						{
  3975  							Name:  "bar",
  3976  							Count: 2,
  3977  						},
  3978  						{
  3979  							Name:  "baz",
  3980  							Count: 2,
  3981  						},
  3982  					},
  3983  				},
  3984  			},
  3985  			New: &Task{
  3986  				Resources: &Resources{
  3987  					Devices: []*RequestedDevice{
  3988  						{
  3989  							Name:  "foo",
  3990  							Count: 2,
  3991  						},
  3992  						{
  3993  							Name:  "bar",
  3994  							Count: 3,
  3995  						},
  3996  						{
  3997  							Name:  "bam",
  3998  							Count: 2,
  3999  						},
  4000  					},
  4001  				},
  4002  			},
  4003  			Expected: &TaskDiff{
  4004  				Type: DiffTypeEdited,
  4005  				Objects: []*ObjectDiff{
  4006  					{
  4007  						Type: DiffTypeEdited,
  4008  						Name: "Resources",
  4009  						Objects: []*ObjectDiff{
  4010  							{
  4011  								Type: DiffTypeEdited,
  4012  								Name: "Device",
  4013  								Fields: []*FieldDiff{
  4014  									{
  4015  										Type: DiffTypeEdited,
  4016  										Name: "Count",
  4017  										Old:  "2",
  4018  										New:  "3",
  4019  									},
  4020  								},
  4021  							},
  4022  							{
  4023  								Type: DiffTypeAdded,
  4024  								Name: "Device",
  4025  								Fields: []*FieldDiff{
  4026  									{
  4027  										Type: DiffTypeAdded,
  4028  										Name: "Count",
  4029  										Old:  "",
  4030  										New:  "2",
  4031  									},
  4032  									{
  4033  										Type: DiffTypeAdded,
  4034  										Name: "Name",
  4035  										Old:  "",
  4036  										New:  "bam",
  4037  									},
  4038  								},
  4039  							},
  4040  							{
  4041  								Type: DiffTypeDeleted,
  4042  								Name: "Device",
  4043  								Fields: []*FieldDiff{
  4044  									{
  4045  										Type: DiffTypeDeleted,
  4046  										Name: "Count",
  4047  										Old:  "2",
  4048  										New:  "",
  4049  									},
  4050  									{
  4051  										Type: DiffTypeDeleted,
  4052  										Name: "Name",
  4053  										Old:  "baz",
  4054  										New:  "",
  4055  									},
  4056  								},
  4057  							},
  4058  						},
  4059  					},
  4060  				},
  4061  			},
  4062  		},
  4063  		{
  4064  			Name:       "Device Resources edited with context",
  4065  			Contextual: true,
  4066  			Old: &Task{
  4067  				Resources: &Resources{
  4068  					CPU:      100,
  4069  					MemoryMB: 100,
  4070  					DiskMB:   100,
  4071  					Devices: []*RequestedDevice{
  4072  						{
  4073  							Name:  "foo",
  4074  							Count: 2,
  4075  						},
  4076  						{
  4077  							Name:  "bar",
  4078  							Count: 2,
  4079  						},
  4080  						{
  4081  							Name:  "baz",
  4082  							Count: 2,
  4083  						},
  4084  					},
  4085  				},
  4086  			},
  4087  			New: &Task{
  4088  				Resources: &Resources{
  4089  					CPU:      100,
  4090  					MemoryMB: 100,
  4091  					DiskMB:   100,
  4092  					Devices: []*RequestedDevice{
  4093  						{
  4094  							Name:  "foo",
  4095  							Count: 2,
  4096  						},
  4097  						{
  4098  							Name:  "bar",
  4099  							Count: 3,
  4100  						},
  4101  						{
  4102  							Name:  "bam",
  4103  							Count: 2,
  4104  						},
  4105  					},
  4106  				},
  4107  			},
  4108  			Expected: &TaskDiff{
  4109  				Type: DiffTypeEdited,
  4110  				Objects: []*ObjectDiff{
  4111  					{
  4112  						Type: DiffTypeEdited,
  4113  						Name: "Resources",
  4114  						Fields: []*FieldDiff{
  4115  							{
  4116  								Type: DiffTypeNone,
  4117  								Name: "CPU",
  4118  								Old:  "100",
  4119  								New:  "100",
  4120  							},
  4121  							{
  4122  								Type: DiffTypeNone,
  4123  								Name: "DiskMB",
  4124  								Old:  "100",
  4125  								New:  "100",
  4126  							},
  4127  							{
  4128  								Type: DiffTypeNone,
  4129  								Name: "IOPS",
  4130  								Old:  "0",
  4131  								New:  "0",
  4132  							},
  4133  							{
  4134  								Type: DiffTypeNone,
  4135  								Name: "MemoryMB",
  4136  								Old:  "100",
  4137  								New:  "100",
  4138  							},
  4139  						},
  4140  						Objects: []*ObjectDiff{
  4141  							{
  4142  								Type: DiffTypeEdited,
  4143  								Name: "Device",
  4144  								Fields: []*FieldDiff{
  4145  									{
  4146  										Type: DiffTypeEdited,
  4147  										Name: "Count",
  4148  										Old:  "2",
  4149  										New:  "3",
  4150  									},
  4151  									{
  4152  										Type: DiffTypeNone,
  4153  										Name: "Name",
  4154  										Old:  "bar",
  4155  										New:  "bar",
  4156  									},
  4157  								},
  4158  							},
  4159  							{
  4160  								Type: DiffTypeAdded,
  4161  								Name: "Device",
  4162  								Fields: []*FieldDiff{
  4163  									{
  4164  										Type: DiffTypeAdded,
  4165  										Name: "Count",
  4166  										Old:  "",
  4167  										New:  "2",
  4168  									},
  4169  									{
  4170  										Type: DiffTypeAdded,
  4171  										Name: "Name",
  4172  										Old:  "",
  4173  										New:  "bam",
  4174  									},
  4175  								},
  4176  							},
  4177  							{
  4178  								Type: DiffTypeDeleted,
  4179  								Name: "Device",
  4180  								Fields: []*FieldDiff{
  4181  									{
  4182  										Type: DiffTypeDeleted,
  4183  										Name: "Count",
  4184  										Old:  "2",
  4185  										New:  "",
  4186  									},
  4187  									{
  4188  										Type: DiffTypeDeleted,
  4189  										Name: "Name",
  4190  										Old:  "baz",
  4191  										New:  "",
  4192  									},
  4193  								},
  4194  							},
  4195  						},
  4196  					},
  4197  				},
  4198  			},
  4199  		},
  4200  		{
  4201  			Name: "Config same",
  4202  			Old: &Task{
  4203  				Config: map[string]interface{}{
  4204  					"foo": 1,
  4205  					"bar": "bar",
  4206  					"bam": []string{"a", "b"},
  4207  					"baz": map[string]int{
  4208  						"a": 1,
  4209  						"b": 2,
  4210  					},
  4211  					"boom": &Port{
  4212  						Label: "boom_port",
  4213  					},
  4214  				},
  4215  			},
  4216  			New: &Task{
  4217  				Config: map[string]interface{}{
  4218  					"foo": 1,
  4219  					"bar": "bar",
  4220  					"bam": []string{"a", "b"},
  4221  					"baz": map[string]int{
  4222  						"a": 1,
  4223  						"b": 2,
  4224  					},
  4225  					"boom": &Port{
  4226  						Label: "boom_port",
  4227  					},
  4228  				},
  4229  			},
  4230  			Expected: &TaskDiff{
  4231  				Type: DiffTypeNone,
  4232  			},
  4233  		},
  4234  		{
  4235  			Name: "Config edited",
  4236  			Old: &Task{
  4237  				Config: map[string]interface{}{
  4238  					"foo": 1,
  4239  					"bar": "baz",
  4240  					"bam": []string{"a", "b"},
  4241  					"baz": map[string]int{
  4242  						"a": 1,
  4243  						"b": 2,
  4244  					},
  4245  					"boom": &Port{
  4246  						Label: "boom_port",
  4247  					},
  4248  				},
  4249  			},
  4250  			New: &Task{
  4251  				Config: map[string]interface{}{
  4252  					"foo": 2,
  4253  					"bar": "baz",
  4254  					"bam": []string{"a", "c", "d"},
  4255  					"baz": map[string]int{
  4256  						"b": 3,
  4257  						"c": 4,
  4258  					},
  4259  					"boom": &Port{
  4260  						Label: "boom_port2",
  4261  					},
  4262  				},
  4263  			},
  4264  			Expected: &TaskDiff{
  4265  				Type: DiffTypeEdited,
  4266  				Objects: []*ObjectDiff{
  4267  					{
  4268  						Type: DiffTypeEdited,
  4269  						Name: "Config",
  4270  						Fields: []*FieldDiff{
  4271  							{
  4272  								Type: DiffTypeEdited,
  4273  								Name: "bam[1]",
  4274  								Old:  "b",
  4275  								New:  "c",
  4276  							},
  4277  							{
  4278  								Type: DiffTypeAdded,
  4279  								Name: "bam[2]",
  4280  								Old:  "",
  4281  								New:  "d",
  4282  							},
  4283  							{
  4284  								Type: DiffTypeDeleted,
  4285  								Name: "baz[a]",
  4286  								Old:  "1",
  4287  								New:  "",
  4288  							},
  4289  							{
  4290  								Type: DiffTypeEdited,
  4291  								Name: "baz[b]",
  4292  								Old:  "2",
  4293  								New:  "3",
  4294  							},
  4295  							{
  4296  								Type: DiffTypeAdded,
  4297  								Name: "baz[c]",
  4298  								Old:  "",
  4299  								New:  "4",
  4300  							},
  4301  							{
  4302  								Type: DiffTypeEdited,
  4303  								Name: "boom.Label",
  4304  								Old:  "boom_port",
  4305  								New:  "boom_port2",
  4306  							},
  4307  							{
  4308  								Type: DiffTypeEdited,
  4309  								Name: "foo",
  4310  								Old:  "1",
  4311  								New:  "2",
  4312  							},
  4313  						},
  4314  					},
  4315  				},
  4316  			},
  4317  		},
  4318  		{
  4319  			Name:       "Config edited with context",
  4320  			Contextual: true,
  4321  			Old: &Task{
  4322  				Config: map[string]interface{}{
  4323  					"foo": 1,
  4324  					"bar": "baz",
  4325  					"bam": []string{"a", "b"},
  4326  					"baz": map[string]int{
  4327  						"a": 1,
  4328  						"b": 2,
  4329  					},
  4330  					"boom": &Port{
  4331  						Label: "boom_port",
  4332  					},
  4333  				},
  4334  			},
  4335  			New: &Task{
  4336  				Config: map[string]interface{}{
  4337  					"foo": 2,
  4338  					"bar": "baz",
  4339  					"bam": []string{"a", "c", "d"},
  4340  					"baz": map[string]int{
  4341  						"a": 1,
  4342  						"b": 2,
  4343  					},
  4344  					"boom": &Port{
  4345  						Label: "boom_port",
  4346  					},
  4347  				},
  4348  			},
  4349  			Expected: &TaskDiff{
  4350  				Type: DiffTypeEdited,
  4351  				Objects: []*ObjectDiff{
  4352  					{
  4353  						Type: DiffTypeEdited,
  4354  						Name: "Config",
  4355  						Fields: []*FieldDiff{
  4356  							{
  4357  								Type: DiffTypeNone,
  4358  								Name: "bam[0]",
  4359  								Old:  "a",
  4360  								New:  "a",
  4361  							},
  4362  							{
  4363  								Type: DiffTypeEdited,
  4364  								Name: "bam[1]",
  4365  								Old:  "b",
  4366  								New:  "c",
  4367  							},
  4368  							{
  4369  								Type: DiffTypeAdded,
  4370  								Name: "bam[2]",
  4371  								Old:  "",
  4372  								New:  "d",
  4373  							},
  4374  							{
  4375  								Type: DiffTypeNone,
  4376  								Name: "bar",
  4377  								Old:  "baz",
  4378  								New:  "baz",
  4379  							},
  4380  							{
  4381  								Type: DiffTypeNone,
  4382  								Name: "baz[a]",
  4383  								Old:  "1",
  4384  								New:  "1",
  4385  							},
  4386  							{
  4387  								Type: DiffTypeNone,
  4388  								Name: "baz[b]",
  4389  								Old:  "2",
  4390  								New:  "2",
  4391  							},
  4392  							{
  4393  								Type: DiffTypeNone,
  4394  								Name: "boom.Label",
  4395  								Old:  "boom_port",
  4396  								New:  "boom_port",
  4397  							},
  4398  							{
  4399  								Type: DiffTypeNone,
  4400  								Name: "boom.To",
  4401  								Old:  "0",
  4402  								New:  "0",
  4403  							},
  4404  							{
  4405  								Type: DiffTypeNone,
  4406  								Name: "boom.Value",
  4407  								Old:  "0",
  4408  								New:  "0",
  4409  							},
  4410  							{
  4411  								Type: DiffTypeEdited,
  4412  								Name: "foo",
  4413  								Old:  "1",
  4414  								New:  "2",
  4415  							},
  4416  						},
  4417  					},
  4418  				},
  4419  			},
  4420  		},
  4421  		{
  4422  			Name: "Services edited (no checks)",
  4423  			Old: &Task{
  4424  				Services: []*Service{
  4425  					{
  4426  						Name:      "foo",
  4427  						PortLabel: "foo",
  4428  					},
  4429  					{
  4430  						Name:      "bar",
  4431  						PortLabel: "bar",
  4432  					},
  4433  					{
  4434  						Name:      "baz",
  4435  						PortLabel: "baz",
  4436  					},
  4437  				},
  4438  			},
  4439  			New: &Task{
  4440  				Services: []*Service{
  4441  					{
  4442  						Name:      "bar",
  4443  						PortLabel: "bar",
  4444  					},
  4445  					{
  4446  						Name:      "baz",
  4447  						PortLabel: "baz2",
  4448  					},
  4449  					{
  4450  						Name:      "bam",
  4451  						PortLabel: "bam",
  4452  					},
  4453  				},
  4454  			},
  4455  			Expected: &TaskDiff{
  4456  				Type: DiffTypeEdited,
  4457  				Objects: []*ObjectDiff{
  4458  					{
  4459  						Type: DiffTypeEdited,
  4460  						Name: "Service",
  4461  						Fields: []*FieldDiff{
  4462  							{
  4463  								Type: DiffTypeEdited,
  4464  								Name: "PortLabel",
  4465  								Old:  "baz",
  4466  								New:  "baz2",
  4467  							},
  4468  						},
  4469  					},
  4470  					{
  4471  						Type: DiffTypeAdded,
  4472  						Name: "Service",
  4473  						Fields: []*FieldDiff{
  4474  							{
  4475  								Type: DiffTypeAdded,
  4476  								Name: "Name",
  4477  								Old:  "",
  4478  								New:  "bam",
  4479  							},
  4480  							{
  4481  								Type: DiffTypeAdded,
  4482  								Name: "PortLabel",
  4483  								Old:  "",
  4484  								New:  "bam",
  4485  							},
  4486  						},
  4487  					},
  4488  					{
  4489  						Type: DiffTypeDeleted,
  4490  						Name: "Service",
  4491  						Fields: []*FieldDiff{
  4492  							{
  4493  								Type: DiffTypeDeleted,
  4494  								Name: "Name",
  4495  								Old:  "foo",
  4496  								New:  "",
  4497  							},
  4498  							{
  4499  								Type: DiffTypeDeleted,
  4500  								Name: "PortLabel",
  4501  								Old:  "foo",
  4502  								New:  "",
  4503  							},
  4504  						},
  4505  					},
  4506  				},
  4507  			},
  4508  		},
  4509  		{
  4510  			Name:       "Services edited (no checks) with context",
  4511  			Contextual: true,
  4512  			Old: &Task{
  4513  				Services: []*Service{
  4514  					{
  4515  						Name:      "foo",
  4516  						PortLabel: "foo",
  4517  					},
  4518  				},
  4519  			},
  4520  			New: &Task{
  4521  				Services: []*Service{
  4522  					{
  4523  						Name:        "foo",
  4524  						PortLabel:   "bar",
  4525  						AddressMode: "driver",
  4526  					},
  4527  				},
  4528  			},
  4529  			Expected: &TaskDiff{
  4530  				Type: DiffTypeEdited,
  4531  				Objects: []*ObjectDiff{
  4532  					{
  4533  						Type: DiffTypeEdited,
  4534  						Name: "Service",
  4535  						Fields: []*FieldDiff{
  4536  							{
  4537  								Type: DiffTypeAdded,
  4538  								Name: "AddressMode",
  4539  								New:  "driver",
  4540  							},
  4541  							{
  4542  								Type: DiffTypeNone,
  4543  								Name: "Name",
  4544  								Old:  "foo",
  4545  								New:  "foo",
  4546  							},
  4547  							{
  4548  								Type: DiffTypeEdited,
  4549  								Name: "PortLabel",
  4550  								Old:  "foo",
  4551  								New:  "bar",
  4552  							},
  4553  						},
  4554  					},
  4555  				},
  4556  			},
  4557  		},
  4558  		{
  4559  			Name:       "Services tags edited (no checks) with context",
  4560  			Contextual: true,
  4561  			Old: &Task{
  4562  				Services: []*Service{
  4563  					{
  4564  						Tags:       []string{"foo", "bar"},
  4565  						CanaryTags: []string{"foo", "bar"},
  4566  					},
  4567  				},
  4568  			},
  4569  			New: &Task{
  4570  				Services: []*Service{
  4571  					{
  4572  						Tags:       []string{"bar", "bam"},
  4573  						CanaryTags: []string{"bar", "bam"},
  4574  					},
  4575  				},
  4576  			},
  4577  			Expected: &TaskDiff{
  4578  				Type: DiffTypeEdited,
  4579  				Objects: []*ObjectDiff{
  4580  					{
  4581  						Type: DiffTypeEdited,
  4582  						Name: "Service",
  4583  						Objects: []*ObjectDiff{
  4584  							{
  4585  								Type: DiffTypeEdited,
  4586  								Name: "CanaryTags",
  4587  								Fields: []*FieldDiff{
  4588  									{
  4589  										Type: DiffTypeAdded,
  4590  										Name: "CanaryTags",
  4591  										Old:  "",
  4592  										New:  "bam",
  4593  									},
  4594  									{
  4595  										Type: DiffTypeNone,
  4596  										Name: "CanaryTags",
  4597  										Old:  "bar",
  4598  										New:  "bar",
  4599  									},
  4600  									{
  4601  										Type: DiffTypeDeleted,
  4602  										Name: "CanaryTags",
  4603  										Old:  "foo",
  4604  										New:  "",
  4605  									},
  4606  								},
  4607  							},
  4608  							{
  4609  								Type: DiffTypeEdited,
  4610  								Name: "Tags",
  4611  								Fields: []*FieldDiff{
  4612  									{
  4613  										Type: DiffTypeAdded,
  4614  										Name: "Tags",
  4615  										Old:  "",
  4616  										New:  "bam",
  4617  									},
  4618  									{
  4619  										Type: DiffTypeNone,
  4620  										Name: "Tags",
  4621  										Old:  "bar",
  4622  										New:  "bar",
  4623  									},
  4624  									{
  4625  										Type: DiffTypeDeleted,
  4626  										Name: "Tags",
  4627  										Old:  "foo",
  4628  										New:  "",
  4629  									},
  4630  								},
  4631  							},
  4632  						},
  4633  						Fields: []*FieldDiff{
  4634  							{
  4635  								Type: DiffTypeNone,
  4636  								Name: "AddressMode",
  4637  							},
  4638  							{
  4639  								Type: DiffTypeNone,
  4640  								Name: "Name",
  4641  							},
  4642  							{
  4643  								Type: DiffTypeNone,
  4644  								Name: "PortLabel",
  4645  							},
  4646  						},
  4647  					},
  4648  				},
  4649  			},
  4650  		},
  4651  
  4652  		{
  4653  			Name: "Service with Connect",
  4654  			Old: &Task{
  4655  				Services: []*Service{
  4656  					{
  4657  						Name: "foo",
  4658  					},
  4659  				},
  4660  			},
  4661  			New: &Task{
  4662  				Services: []*Service{
  4663  					{
  4664  						Name: "foo",
  4665  						Connect: &ConsulConnect{
  4666  							SidecarService: &ConsulSidecarService{},
  4667  						},
  4668  					},
  4669  				},
  4670  			},
  4671  			Expected: &TaskDiff{
  4672  				Type: DiffTypeEdited,
  4673  				Objects: []*ObjectDiff{
  4674  					{
  4675  						Type: DiffTypeEdited,
  4676  						Name: "Service",
  4677  						Objects: []*ObjectDiff{
  4678  							{
  4679  								Type: DiffTypeAdded,
  4680  								Name: "ConsulConnect",
  4681  								Fields: []*FieldDiff{
  4682  									{
  4683  										Type: DiffTypeAdded,
  4684  										Name: "Native",
  4685  										Old:  "",
  4686  										New:  "false",
  4687  									},
  4688  								},
  4689  								Objects: []*ObjectDiff{
  4690  									{
  4691  										Type: DiffTypeAdded,
  4692  										Name: "SidecarService",
  4693  									},
  4694  								},
  4695  							},
  4696  						},
  4697  					},
  4698  				},
  4699  			},
  4700  		},
  4701  
  4702  		{
  4703  			Name: "Service Checks edited",
  4704  			Old: &Task{
  4705  				Services: []*Service{
  4706  					{
  4707  						Name: "foo",
  4708  						Checks: []*ServiceCheck{
  4709  							{
  4710  								Name:     "foo",
  4711  								Type:     "http",
  4712  								Command:  "foo",
  4713  								Args:     []string{"foo"},
  4714  								Path:     "foo",
  4715  								Protocol: "http",
  4716  								Interval: 1 * time.Second,
  4717  								Timeout:  1 * time.Second,
  4718  								Header: map[string][]string{
  4719  									"Foo": {"bar"},
  4720  								},
  4721  							},
  4722  							{
  4723  								Name:     "bar",
  4724  								Type:     "http",
  4725  								Command:  "foo",
  4726  								Args:     []string{"foo"},
  4727  								Path:     "foo",
  4728  								Protocol: "http",
  4729  								Interval: 1 * time.Second,
  4730  								Timeout:  1 * time.Second,
  4731  							},
  4732  							{
  4733  								Name:     "baz",
  4734  								Type:     "http",
  4735  								Command:  "foo",
  4736  								Args:     []string{"foo"},
  4737  								Path:     "foo",
  4738  								Protocol: "http",
  4739  								Interval: 1 * time.Second,
  4740  								Timeout:  1 * time.Second,
  4741  							},
  4742  						},
  4743  					},
  4744  				},
  4745  			},
  4746  			New: &Task{
  4747  				Services: []*Service{
  4748  					{
  4749  						Name: "foo",
  4750  						Checks: []*ServiceCheck{
  4751  							{
  4752  								Name:     "bar",
  4753  								Type:     "http",
  4754  								Command:  "foo",
  4755  								Args:     []string{"foo"},
  4756  								Path:     "foo",
  4757  								Protocol: "http",
  4758  								Interval: 1 * time.Second,
  4759  								Timeout:  1 * time.Second,
  4760  							},
  4761  							{
  4762  								Name:     "baz",
  4763  								Type:     "tcp",
  4764  								Command:  "foo",
  4765  								Args:     []string{"foo"},
  4766  								Path:     "foo",
  4767  								Protocol: "http",
  4768  								Interval: 1 * time.Second,
  4769  								Timeout:  1 * time.Second,
  4770  								Header: map[string][]string{
  4771  									"Eggs": {"spam"},
  4772  								},
  4773  							},
  4774  							{
  4775  								Name:     "bam",
  4776  								Type:     "http",
  4777  								Command:  "foo",
  4778  								Args:     []string{"foo"},
  4779  								Path:     "foo",
  4780  								Protocol: "http",
  4781  								Interval: 1 * time.Second,
  4782  								Timeout:  1 * time.Second,
  4783  							},
  4784  						},
  4785  					},
  4786  				},
  4787  			},
  4788  			Expected: &TaskDiff{
  4789  				Type: DiffTypeEdited,
  4790  				Objects: []*ObjectDiff{
  4791  					{
  4792  						Type: DiffTypeEdited,
  4793  						Name: "Service",
  4794  						Objects: []*ObjectDiff{
  4795  							{
  4796  								Type: DiffTypeEdited,
  4797  								Name: "Check",
  4798  								Fields: []*FieldDiff{
  4799  									{
  4800  										Type: DiffTypeEdited,
  4801  										Name: "Type",
  4802  										Old:  "http",
  4803  										New:  "tcp",
  4804  									},
  4805  								},
  4806  								Objects: []*ObjectDiff{
  4807  									{
  4808  										Type: DiffTypeAdded,
  4809  										Name: "Header",
  4810  										Fields: []*FieldDiff{
  4811  											{
  4812  												Type: DiffTypeAdded,
  4813  												Name: "Eggs[0]",
  4814  												Old:  "",
  4815  												New:  "spam",
  4816  											},
  4817  										},
  4818  									},
  4819  								},
  4820  							},
  4821  							{
  4822  								Type: DiffTypeAdded,
  4823  								Name: "Check",
  4824  								Fields: []*FieldDiff{
  4825  									{
  4826  										Type: DiffTypeAdded,
  4827  										Name: "Command",
  4828  										Old:  "",
  4829  										New:  "foo",
  4830  									},
  4831  									{
  4832  										Type: DiffTypeAdded,
  4833  										Name: "GRPCUseTLS",
  4834  										Old:  "",
  4835  										New:  "false",
  4836  									},
  4837  									{
  4838  										Type: DiffTypeAdded,
  4839  										Name: "Interval",
  4840  										Old:  "",
  4841  										New:  "1000000000",
  4842  									},
  4843  									{
  4844  										Type: DiffTypeAdded,
  4845  										Name: "Name",
  4846  										Old:  "",
  4847  										New:  "bam",
  4848  									},
  4849  									{
  4850  										Type: DiffTypeAdded,
  4851  										Name: "Path",
  4852  										Old:  "",
  4853  										New:  "foo",
  4854  									},
  4855  									{
  4856  										Type: DiffTypeAdded,
  4857  										Name: "Protocol",
  4858  										Old:  "",
  4859  										New:  "http",
  4860  									},
  4861  									{
  4862  										Type: DiffTypeAdded,
  4863  										Name: "TLSSkipVerify",
  4864  										Old:  "",
  4865  										New:  "false",
  4866  									},
  4867  									{
  4868  										Type: DiffTypeAdded,
  4869  										Name: "Timeout",
  4870  										Old:  "",
  4871  										New:  "1000000000",
  4872  									},
  4873  									{
  4874  										Type: DiffTypeAdded,
  4875  										Name: "Type",
  4876  										Old:  "",
  4877  										New:  "http",
  4878  									},
  4879  								},
  4880  							},
  4881  							{
  4882  								Type: DiffTypeDeleted,
  4883  								Name: "Check",
  4884  								Fields: []*FieldDiff{
  4885  									{
  4886  										Type: DiffTypeDeleted,
  4887  										Name: "Command",
  4888  										Old:  "foo",
  4889  										New:  "",
  4890  									},
  4891  									{
  4892  										Type: DiffTypeDeleted,
  4893  										Name: "GRPCUseTLS",
  4894  										Old:  "false",
  4895  										New:  "",
  4896  									},
  4897  									{
  4898  										Type: DiffTypeDeleted,
  4899  										Name: "Interval",
  4900  										Old:  "1000000000",
  4901  										New:  "",
  4902  									},
  4903  									{
  4904  										Type: DiffTypeDeleted,
  4905  										Name: "Name",
  4906  										Old:  "foo",
  4907  										New:  "",
  4908  									},
  4909  									{
  4910  										Type: DiffTypeDeleted,
  4911  										Name: "Path",
  4912  										Old:  "foo",
  4913  										New:  "",
  4914  									},
  4915  									{
  4916  										Type: DiffTypeDeleted,
  4917  										Name: "Protocol",
  4918  										Old:  "http",
  4919  										New:  "",
  4920  									},
  4921  									{
  4922  										Type: DiffTypeDeleted,
  4923  										Name: "TLSSkipVerify",
  4924  										Old:  "false",
  4925  										New:  "",
  4926  									},
  4927  									{
  4928  										Type: DiffTypeDeleted,
  4929  										Name: "Timeout",
  4930  										Old:  "1000000000",
  4931  										New:  "",
  4932  									},
  4933  									{
  4934  										Type: DiffTypeDeleted,
  4935  										Name: "Type",
  4936  										Old:  "http",
  4937  										New:  "",
  4938  									},
  4939  								},
  4940  								Objects: []*ObjectDiff{
  4941  									{
  4942  										Type: DiffTypeDeleted,
  4943  										Name: "Header",
  4944  										Fields: []*FieldDiff{
  4945  											{
  4946  												Type: DiffTypeDeleted,
  4947  												Name: "Foo[0]",
  4948  												Old:  "bar",
  4949  											},
  4950  										},
  4951  									},
  4952  								},
  4953  							},
  4954  						},
  4955  					},
  4956  				},
  4957  			},
  4958  		},
  4959  		{
  4960  			Name:       "Service Checks edited with context",
  4961  			Contextual: true,
  4962  			Old: &Task{
  4963  				Services: []*Service{
  4964  					{
  4965  						Name: "foo",
  4966  						Checks: []*ServiceCheck{
  4967  							{
  4968  								Name:          "foo",
  4969  								Type:          "http",
  4970  								Command:       "foo",
  4971  								Args:          []string{"foo"},
  4972  								Path:          "foo",
  4973  								Protocol:      "http",
  4974  								Interval:      1 * time.Second,
  4975  								Timeout:       1 * time.Second,
  4976  								InitialStatus: "critical",
  4977  								Header: map[string][]string{
  4978  									"Foo": {"bar"},
  4979  								},
  4980  							},
  4981  						},
  4982  					},
  4983  				},
  4984  			},
  4985  			New: &Task{
  4986  				Services: []*Service{
  4987  					{
  4988  						Name: "foo",
  4989  						Checks: []*ServiceCheck{
  4990  							{
  4991  								Name:          "foo",
  4992  								Type:          "tcp",
  4993  								Command:       "foo",
  4994  								Args:          []string{"foo"},
  4995  								Path:          "foo",
  4996  								Protocol:      "http",
  4997  								Interval:      1 * time.Second,
  4998  								Timeout:       1 * time.Second,
  4999  								InitialStatus: "passing",
  5000  								Method:        "POST",
  5001  								Header: map[string][]string{
  5002  									"Foo":  {"bar", "baz"},
  5003  									"Eggs": {"spam"},
  5004  								},
  5005  							},
  5006  						},
  5007  					},
  5008  				},
  5009  			},
  5010  			Expected: &TaskDiff{
  5011  				Type: DiffTypeEdited,
  5012  				Objects: []*ObjectDiff{
  5013  					{
  5014  						Type: DiffTypeEdited,
  5015  						Name: "Service",
  5016  						Fields: []*FieldDiff{
  5017  							{
  5018  								Type: DiffTypeNone,
  5019  								Name: "AddressMode",
  5020  								Old:  "",
  5021  								New:  "",
  5022  							},
  5023  							{
  5024  								Type: DiffTypeNone,
  5025  								Name: "Name",
  5026  								Old:  "foo",
  5027  								New:  "foo",
  5028  							},
  5029  							{
  5030  								Type: DiffTypeNone,
  5031  								Name: "PortLabel",
  5032  								Old:  "",
  5033  								New:  "",
  5034  							},
  5035  						},
  5036  						Objects: []*ObjectDiff{
  5037  							{
  5038  								Type: DiffTypeEdited,
  5039  								Name: "Check",
  5040  								Fields: []*FieldDiff{
  5041  									{
  5042  										Type: DiffTypeNone,
  5043  										Name: "AddressMode",
  5044  										Old:  "",
  5045  										New:  "",
  5046  									},
  5047  									{
  5048  										Type: DiffTypeNone,
  5049  										Name: "Command",
  5050  										Old:  "foo",
  5051  										New:  "foo",
  5052  									},
  5053  									{
  5054  										Type: DiffTypeNone,
  5055  										Name: "GRPCService",
  5056  										Old:  "",
  5057  										New:  "",
  5058  									},
  5059  									{
  5060  										Type: DiffTypeNone,
  5061  										Name: "GRPCUseTLS",
  5062  										Old:  "false",
  5063  										New:  "false",
  5064  									},
  5065  									{
  5066  										Type: DiffTypeEdited,
  5067  										Name: "InitialStatus",
  5068  										Old:  "critical",
  5069  										New:  "passing",
  5070  									},
  5071  									{
  5072  										Type: DiffTypeNone,
  5073  										Name: "Interval",
  5074  										Old:  "1000000000",
  5075  										New:  "1000000000",
  5076  									},
  5077  									{
  5078  										Type: DiffTypeAdded,
  5079  										Name: "Method",
  5080  										Old:  "",
  5081  										New:  "POST",
  5082  									},
  5083  									{
  5084  										Type: DiffTypeNone,
  5085  										Name: "Name",
  5086  										Old:  "foo",
  5087  										New:  "foo",
  5088  									},
  5089  									{
  5090  										Type: DiffTypeNone,
  5091  										Name: "Path",
  5092  										Old:  "foo",
  5093  										New:  "foo",
  5094  									},
  5095  									{
  5096  										Type: DiffTypeNone,
  5097  										Name: "PortLabel",
  5098  										Old:  "",
  5099  										New:  "",
  5100  									},
  5101  									{
  5102  										Type: DiffTypeNone,
  5103  										Name: "Protocol",
  5104  										Old:  "http",
  5105  										New:  "http",
  5106  									},
  5107  									{
  5108  										Type: DiffTypeNone,
  5109  										Name: "TLSSkipVerify",
  5110  										Old:  "false",
  5111  										New:  "false",
  5112  									},
  5113  									{
  5114  										Type: DiffTypeNone,
  5115  										Name: "TaskName",
  5116  										Old:  "",
  5117  										New:  "",
  5118  									},
  5119  									{
  5120  										Type: DiffTypeNone,
  5121  										Name: "Timeout",
  5122  										Old:  "1000000000",
  5123  										New:  "1000000000",
  5124  									},
  5125  									{
  5126  										Type: DiffTypeEdited,
  5127  										Name: "Type",
  5128  										Old:  "http",
  5129  										New:  "tcp",
  5130  									},
  5131  								},
  5132  								Objects: []*ObjectDiff{
  5133  									{
  5134  										Type: DiffTypeEdited,
  5135  										Name: "Header",
  5136  										Fields: []*FieldDiff{
  5137  											{
  5138  												Type: DiffTypeAdded,
  5139  												Name: "Eggs[0]",
  5140  												Old:  "",
  5141  												New:  "spam",
  5142  											},
  5143  											{
  5144  												Type: DiffTypeNone,
  5145  												Name: "Foo[0]",
  5146  												Old:  "bar",
  5147  												New:  "bar",
  5148  											},
  5149  											{
  5150  												Type: DiffTypeAdded,
  5151  												Name: "Foo[1]",
  5152  												Old:  "",
  5153  												New:  "baz",
  5154  											},
  5155  										},
  5156  									},
  5157  								},
  5158  							},
  5159  						},
  5160  					},
  5161  				},
  5162  			},
  5163  		},
  5164  		{
  5165  			Name: "CheckRestart edited",
  5166  			Old: &Task{
  5167  				Services: []*Service{
  5168  					{
  5169  						Name: "foo",
  5170  						Checks: []*ServiceCheck{
  5171  							{
  5172  								Name:     "foo",
  5173  								Type:     "http",
  5174  								Command:  "foo",
  5175  								Args:     []string{"foo"},
  5176  								Path:     "foo",
  5177  								Protocol: "http",
  5178  								Interval: 1 * time.Second,
  5179  								Timeout:  1 * time.Second,
  5180  							},
  5181  							{
  5182  								Name:     "bar",
  5183  								Type:     "http",
  5184  								Command:  "foo",
  5185  								Args:     []string{"foo"},
  5186  								Path:     "foo",
  5187  								Protocol: "http",
  5188  								Interval: 1 * time.Second,
  5189  								Timeout:  1 * time.Second,
  5190  								CheckRestart: &CheckRestart{
  5191  									Limit: 2,
  5192  									Grace: 2 * time.Second,
  5193  								},
  5194  							},
  5195  							{
  5196  								Name:     "baz",
  5197  								Type:     "http",
  5198  								Command:  "foo",
  5199  								Args:     []string{"foo"},
  5200  								Path:     "foo",
  5201  								Protocol: "http",
  5202  								Interval: 1 * time.Second,
  5203  								Timeout:  1 * time.Second,
  5204  								CheckRestart: &CheckRestart{
  5205  									Limit: 3,
  5206  									Grace: 3 * time.Second,
  5207  								},
  5208  							},
  5209  						},
  5210  					},
  5211  				},
  5212  			},
  5213  			New: &Task{
  5214  				Services: []*Service{
  5215  					{
  5216  						Name: "foo",
  5217  						Checks: []*ServiceCheck{
  5218  							{
  5219  								Name:     "foo",
  5220  								Type:     "http",
  5221  								Command:  "foo",
  5222  								Args:     []string{"foo"},
  5223  								Path:     "foo",
  5224  								Protocol: "http",
  5225  								Interval: 1 * time.Second,
  5226  								Timeout:  1 * time.Second,
  5227  								CheckRestart: &CheckRestart{
  5228  									Limit: 1,
  5229  									Grace: 1 * time.Second,
  5230  								},
  5231  							},
  5232  							{
  5233  								Name:     "bar",
  5234  								Type:     "http",
  5235  								Command:  "foo",
  5236  								Args:     []string{"foo"},
  5237  								Path:     "foo",
  5238  								Protocol: "http",
  5239  								Interval: 1 * time.Second,
  5240  								Timeout:  1 * time.Second,
  5241  							},
  5242  							{
  5243  								Name:     "baz",
  5244  								Type:     "http",
  5245  								Command:  "foo",
  5246  								Args:     []string{"foo"},
  5247  								Path:     "foo",
  5248  								Protocol: "http",
  5249  								Interval: 1 * time.Second,
  5250  								Timeout:  1 * time.Second,
  5251  								CheckRestart: &CheckRestart{
  5252  									Limit: 4,
  5253  									Grace: 4 * time.Second,
  5254  								},
  5255  							},
  5256  						},
  5257  					},
  5258  				},
  5259  			},
  5260  			Expected: &TaskDiff{
  5261  				Type: DiffTypeEdited,
  5262  				Objects: []*ObjectDiff{
  5263  					{
  5264  						Type: DiffTypeEdited,
  5265  						Name: "Service",
  5266  						Objects: []*ObjectDiff{
  5267  							{
  5268  								Type: DiffTypeEdited,
  5269  								Name: "Check",
  5270  								Objects: []*ObjectDiff{
  5271  									{
  5272  										Type: DiffTypeEdited,
  5273  										Name: "CheckRestart",
  5274  										Fields: []*FieldDiff{
  5275  											{
  5276  												Type: DiffTypeEdited,
  5277  												Name: "Grace",
  5278  												Old:  "3000000000",
  5279  												New:  "4000000000",
  5280  											},
  5281  											{
  5282  												Type: DiffTypeEdited,
  5283  												Name: "Limit",
  5284  												Old:  "3",
  5285  												New:  "4",
  5286  											},
  5287  										},
  5288  									},
  5289  								},
  5290  							},
  5291  							{
  5292  								Type: DiffTypeEdited,
  5293  								Name: "Check",
  5294  								Objects: []*ObjectDiff{
  5295  									{
  5296  										Type: DiffTypeAdded,
  5297  										Name: "CheckRestart",
  5298  										Fields: []*FieldDiff{
  5299  											{
  5300  												Type: DiffTypeAdded,
  5301  												Name: "Grace",
  5302  												New:  "1000000000",
  5303  											},
  5304  											{
  5305  												Type: DiffTypeAdded,
  5306  												Name: "IgnoreWarnings",
  5307  												New:  "false",
  5308  											},
  5309  											{
  5310  												Type: DiffTypeAdded,
  5311  												Name: "Limit",
  5312  												New:  "1",
  5313  											},
  5314  										},
  5315  									},
  5316  								},
  5317  							},
  5318  							{
  5319  								Type: DiffTypeEdited,
  5320  								Name: "Check",
  5321  								Objects: []*ObjectDiff{
  5322  									{
  5323  										Type: DiffTypeDeleted,
  5324  										Name: "CheckRestart",
  5325  										Fields: []*FieldDiff{
  5326  											{
  5327  												Type: DiffTypeDeleted,
  5328  												Name: "Grace",
  5329  												Old:  "2000000000",
  5330  											},
  5331  											{
  5332  												Type: DiffTypeDeleted,
  5333  												Name: "IgnoreWarnings",
  5334  												Old:  "false",
  5335  											},
  5336  											{
  5337  												Type: DiffTypeDeleted,
  5338  												Name: "Limit",
  5339  												Old:  "2",
  5340  											},
  5341  										},
  5342  									},
  5343  								},
  5344  							},
  5345  						},
  5346  					},
  5347  				},
  5348  			},
  5349  		},
  5350  		{
  5351  			Name: "Vault added",
  5352  			Old:  &Task{},
  5353  			New: &Task{
  5354  				Vault: &Vault{
  5355  					Policies:     []string{"foo", "bar"},
  5356  					Env:          true,
  5357  					ChangeMode:   "signal",
  5358  					ChangeSignal: "SIGUSR1",
  5359  				},
  5360  			},
  5361  			Expected: &TaskDiff{
  5362  				Type: DiffTypeEdited,
  5363  				Objects: []*ObjectDiff{
  5364  					{
  5365  						Type: DiffTypeAdded,
  5366  						Name: "Vault",
  5367  						Fields: []*FieldDiff{
  5368  							{
  5369  								Type: DiffTypeAdded,
  5370  								Name: "ChangeMode",
  5371  								Old:  "",
  5372  								New:  "signal",
  5373  							},
  5374  							{
  5375  								Type: DiffTypeAdded,
  5376  								Name: "ChangeSignal",
  5377  								Old:  "",
  5378  								New:  "SIGUSR1",
  5379  							},
  5380  							{
  5381  								Type: DiffTypeAdded,
  5382  								Name: "Env",
  5383  								Old:  "",
  5384  								New:  "true",
  5385  							},
  5386  						},
  5387  						Objects: []*ObjectDiff{
  5388  							{
  5389  								Type: DiffTypeAdded,
  5390  								Name: "Policies",
  5391  								Fields: []*FieldDiff{
  5392  									{
  5393  										Type: DiffTypeAdded,
  5394  										Name: "Policies",
  5395  										Old:  "",
  5396  										New:  "bar",
  5397  									},
  5398  									{
  5399  										Type: DiffTypeAdded,
  5400  										Name: "Policies",
  5401  										Old:  "",
  5402  										New:  "foo",
  5403  									},
  5404  								},
  5405  							},
  5406  						},
  5407  					},
  5408  				},
  5409  			},
  5410  		},
  5411  		{
  5412  			Name: "Vault deleted",
  5413  			Old: &Task{
  5414  				Vault: &Vault{
  5415  					Policies:     []string{"foo", "bar"},
  5416  					Env:          true,
  5417  					ChangeMode:   "signal",
  5418  					ChangeSignal: "SIGUSR1",
  5419  				},
  5420  			},
  5421  			New: &Task{},
  5422  			Expected: &TaskDiff{
  5423  				Type: DiffTypeEdited,
  5424  				Objects: []*ObjectDiff{
  5425  					{
  5426  						Type: DiffTypeDeleted,
  5427  						Name: "Vault",
  5428  						Fields: []*FieldDiff{
  5429  							{
  5430  								Type: DiffTypeDeleted,
  5431  								Name: "ChangeMode",
  5432  								Old:  "signal",
  5433  								New:  "",
  5434  							},
  5435  							{
  5436  								Type: DiffTypeDeleted,
  5437  								Name: "ChangeSignal",
  5438  								Old:  "SIGUSR1",
  5439  								New:  "",
  5440  							},
  5441  							{
  5442  								Type: DiffTypeDeleted,
  5443  								Name: "Env",
  5444  								Old:  "true",
  5445  								New:  "",
  5446  							},
  5447  						},
  5448  						Objects: []*ObjectDiff{
  5449  							{
  5450  								Type: DiffTypeDeleted,
  5451  								Name: "Policies",
  5452  								Fields: []*FieldDiff{
  5453  									{
  5454  										Type: DiffTypeDeleted,
  5455  										Name: "Policies",
  5456  										Old:  "bar",
  5457  										New:  "",
  5458  									},
  5459  									{
  5460  										Type: DiffTypeDeleted,
  5461  										Name: "Policies",
  5462  										Old:  "foo",
  5463  										New:  "",
  5464  									},
  5465  								},
  5466  							},
  5467  						},
  5468  					},
  5469  				},
  5470  			},
  5471  		},
  5472  		{
  5473  			Name: "Vault edited",
  5474  			Old: &Task{
  5475  				Vault: &Vault{
  5476  					Policies:     []string{"foo", "bar"},
  5477  					Env:          true,
  5478  					ChangeMode:   "signal",
  5479  					ChangeSignal: "SIGUSR1",
  5480  				},
  5481  			},
  5482  			New: &Task{
  5483  				Vault: &Vault{
  5484  					Policies:     []string{"bar", "baz"},
  5485  					Env:          false,
  5486  					ChangeMode:   "restart",
  5487  					ChangeSignal: "foo",
  5488  				},
  5489  			},
  5490  			Expected: &TaskDiff{
  5491  				Type: DiffTypeEdited,
  5492  				Objects: []*ObjectDiff{
  5493  					{
  5494  						Type: DiffTypeEdited,
  5495  						Name: "Vault",
  5496  						Fields: []*FieldDiff{
  5497  							{
  5498  								Type: DiffTypeEdited,
  5499  								Name: "ChangeMode",
  5500  								Old:  "signal",
  5501  								New:  "restart",
  5502  							},
  5503  							{
  5504  								Type: DiffTypeEdited,
  5505  								Name: "ChangeSignal",
  5506  								Old:  "SIGUSR1",
  5507  								New:  "foo",
  5508  							},
  5509  							{
  5510  								Type: DiffTypeEdited,
  5511  								Name: "Env",
  5512  								Old:  "true",
  5513  								New:  "false",
  5514  							},
  5515  						},
  5516  						Objects: []*ObjectDiff{
  5517  							{
  5518  								Type: DiffTypeEdited,
  5519  								Name: "Policies",
  5520  								Fields: []*FieldDiff{
  5521  									{
  5522  										Type: DiffTypeAdded,
  5523  										Name: "Policies",
  5524  										Old:  "",
  5525  										New:  "baz",
  5526  									},
  5527  									{
  5528  										Type: DiffTypeDeleted,
  5529  										Name: "Policies",
  5530  										Old:  "foo",
  5531  										New:  "",
  5532  									},
  5533  								},
  5534  							},
  5535  						},
  5536  					},
  5537  				},
  5538  			},
  5539  		},
  5540  		{
  5541  			Name:       "Vault edited with context",
  5542  			Contextual: true,
  5543  			Old: &Task{
  5544  				Vault: &Vault{
  5545  					Policies:     []string{"foo", "bar"},
  5546  					Env:          true,
  5547  					ChangeMode:   "signal",
  5548  					ChangeSignal: "SIGUSR1",
  5549  				},
  5550  			},
  5551  			New: &Task{
  5552  				Vault: &Vault{
  5553  					Policies:     []string{"bar", "baz"},
  5554  					Env:          true,
  5555  					ChangeMode:   "signal",
  5556  					ChangeSignal: "SIGUSR1",
  5557  				},
  5558  			},
  5559  			Expected: &TaskDiff{
  5560  				Type: DiffTypeEdited,
  5561  				Objects: []*ObjectDiff{
  5562  					{
  5563  						Type: DiffTypeEdited,
  5564  						Name: "Vault",
  5565  						Fields: []*FieldDiff{
  5566  							{
  5567  								Type: DiffTypeNone,
  5568  								Name: "ChangeMode",
  5569  								Old:  "signal",
  5570  								New:  "signal",
  5571  							},
  5572  							{
  5573  								Type: DiffTypeNone,
  5574  								Name: "ChangeSignal",
  5575  								Old:  "SIGUSR1",
  5576  								New:  "SIGUSR1",
  5577  							},
  5578  							{
  5579  								Type: DiffTypeNone,
  5580  								Name: "Env",
  5581  								Old:  "true",
  5582  								New:  "true",
  5583  							},
  5584  						},
  5585  						Objects: []*ObjectDiff{
  5586  							{
  5587  								Type: DiffTypeEdited,
  5588  								Name: "Policies",
  5589  								Fields: []*FieldDiff{
  5590  									{
  5591  										Type: DiffTypeAdded,
  5592  										Name: "Policies",
  5593  										Old:  "",
  5594  										New:  "baz",
  5595  									},
  5596  									{
  5597  										Type: DiffTypeNone,
  5598  										Name: "Policies",
  5599  										Old:  "bar",
  5600  										New:  "bar",
  5601  									},
  5602  									{
  5603  										Type: DiffTypeDeleted,
  5604  										Name: "Policies",
  5605  										Old:  "foo",
  5606  										New:  "",
  5607  									},
  5608  								},
  5609  							},
  5610  						},
  5611  					},
  5612  				},
  5613  			},
  5614  		},
  5615  		{
  5616  			Name: "Template edited",
  5617  			Old: &Task{
  5618  				Templates: []*Template{
  5619  					{
  5620  						SourcePath:   "foo",
  5621  						DestPath:     "bar",
  5622  						EmbeddedTmpl: "baz",
  5623  						ChangeMode:   "bam",
  5624  						ChangeSignal: "SIGHUP",
  5625  						Splay:        1,
  5626  						Perms:        "0644",
  5627  						VaultGrace:   3 * time.Second,
  5628  					},
  5629  					{
  5630  						SourcePath:   "foo2",
  5631  						DestPath:     "bar2",
  5632  						EmbeddedTmpl: "baz2",
  5633  						ChangeMode:   "bam2",
  5634  						ChangeSignal: "SIGHUP2",
  5635  						Splay:        2,
  5636  						Perms:        "0666",
  5637  						Envvars:      true,
  5638  						VaultGrace:   5 * time.Second,
  5639  					},
  5640  				},
  5641  			},
  5642  			New: &Task{
  5643  				Templates: []*Template{
  5644  					{
  5645  						SourcePath:   "foo",
  5646  						DestPath:     "bar",
  5647  						EmbeddedTmpl: "baz",
  5648  						ChangeMode:   "bam",
  5649  						ChangeSignal: "SIGHUP",
  5650  						Splay:        1,
  5651  						Perms:        "0644",
  5652  						VaultGrace:   3 * time.Second,
  5653  					},
  5654  					{
  5655  						SourcePath:   "foo3",
  5656  						DestPath:     "bar3",
  5657  						EmbeddedTmpl: "baz3",
  5658  						ChangeMode:   "bam3",
  5659  						ChangeSignal: "SIGHUP3",
  5660  						Splay:        3,
  5661  						Perms:        "0776",
  5662  						VaultGrace:   10 * time.Second,
  5663  					},
  5664  				},
  5665  			},
  5666  			Expected: &TaskDiff{
  5667  				Type: DiffTypeEdited,
  5668  				Objects: []*ObjectDiff{
  5669  					{
  5670  						Type: DiffTypeAdded,
  5671  						Name: "Template",
  5672  						Fields: []*FieldDiff{
  5673  							{
  5674  								Type: DiffTypeAdded,
  5675  								Name: "ChangeMode",
  5676  								Old:  "",
  5677  								New:  "bam3",
  5678  							},
  5679  							{
  5680  								Type: DiffTypeAdded,
  5681  								Name: "ChangeSignal",
  5682  								Old:  "",
  5683  								New:  "SIGHUP3",
  5684  							},
  5685  							{
  5686  								Type: DiffTypeAdded,
  5687  								Name: "DestPath",
  5688  								Old:  "",
  5689  								New:  "bar3",
  5690  							},
  5691  							{
  5692  								Type: DiffTypeAdded,
  5693  								Name: "EmbeddedTmpl",
  5694  								Old:  "",
  5695  								New:  "baz3",
  5696  							},
  5697  							{
  5698  								Type: DiffTypeAdded,
  5699  								Name: "Envvars",
  5700  								Old:  "",
  5701  								New:  "false",
  5702  							},
  5703  							{
  5704  								Type: DiffTypeAdded,
  5705  								Name: "Perms",
  5706  								Old:  "",
  5707  								New:  "0776",
  5708  							},
  5709  							{
  5710  								Type: DiffTypeAdded,
  5711  								Name: "SourcePath",
  5712  								Old:  "",
  5713  								New:  "foo3",
  5714  							},
  5715  							{
  5716  								Type: DiffTypeAdded,
  5717  								Name: "Splay",
  5718  								Old:  "",
  5719  								New:  "3",
  5720  							},
  5721  							{
  5722  								Type: DiffTypeAdded,
  5723  								Name: "VaultGrace",
  5724  								Old:  "",
  5725  								New:  "10000000000",
  5726  							},
  5727  						},
  5728  					},
  5729  					{
  5730  						Type: DiffTypeDeleted,
  5731  						Name: "Template",
  5732  						Fields: []*FieldDiff{
  5733  							{
  5734  								Type: DiffTypeDeleted,
  5735  								Name: "ChangeMode",
  5736  								Old:  "bam2",
  5737  								New:  "",
  5738  							},
  5739  							{
  5740  								Type: DiffTypeDeleted,
  5741  								Name: "ChangeSignal",
  5742  								Old:  "SIGHUP2",
  5743  								New:  "",
  5744  							},
  5745  							{
  5746  								Type: DiffTypeDeleted,
  5747  								Name: "DestPath",
  5748  								Old:  "bar2",
  5749  								New:  "",
  5750  							},
  5751  							{
  5752  								Type: DiffTypeDeleted,
  5753  								Name: "EmbeddedTmpl",
  5754  								Old:  "baz2",
  5755  								New:  "",
  5756  							},
  5757  							{
  5758  								Type: DiffTypeDeleted,
  5759  								Name: "Envvars",
  5760  								Old:  "true",
  5761  								New:  "",
  5762  							},
  5763  							{
  5764  								Type: DiffTypeDeleted,
  5765  								Name: "Perms",
  5766  								Old:  "0666",
  5767  								New:  "",
  5768  							},
  5769  							{
  5770  								Type: DiffTypeDeleted,
  5771  								Name: "SourcePath",
  5772  								Old:  "foo2",
  5773  								New:  "",
  5774  							},
  5775  							{
  5776  								Type: DiffTypeDeleted,
  5777  								Name: "Splay",
  5778  								Old:  "2",
  5779  								New:  "",
  5780  							},
  5781  							{
  5782  								Type: DiffTypeDeleted,
  5783  								Name: "VaultGrace",
  5784  								Old:  "5000000000",
  5785  								New:  "",
  5786  							},
  5787  						},
  5788  					},
  5789  				},
  5790  			},
  5791  		},
  5792  		{
  5793  			Name: "DispatchPayload added",
  5794  			Old:  &Task{},
  5795  			New: &Task{
  5796  				DispatchPayload: &DispatchPayloadConfig{
  5797  					File: "foo",
  5798  				},
  5799  			},
  5800  			Expected: &TaskDiff{
  5801  				Type: DiffTypeEdited,
  5802  				Objects: []*ObjectDiff{
  5803  					{
  5804  						Type: DiffTypeAdded,
  5805  						Name: "DispatchPayload",
  5806  						Fields: []*FieldDiff{
  5807  							{
  5808  								Type: DiffTypeAdded,
  5809  								Name: "File",
  5810  								Old:  "",
  5811  								New:  "foo",
  5812  							},
  5813  						},
  5814  					},
  5815  				},
  5816  			},
  5817  		},
  5818  		{
  5819  			Name: "DispatchPayload deleted",
  5820  			Old: &Task{
  5821  				DispatchPayload: &DispatchPayloadConfig{
  5822  					File: "foo",
  5823  				},
  5824  			},
  5825  			New: &Task{},
  5826  			Expected: &TaskDiff{
  5827  				Type: DiffTypeEdited,
  5828  				Objects: []*ObjectDiff{
  5829  					{
  5830  						Type: DiffTypeDeleted,
  5831  						Name: "DispatchPayload",
  5832  						Fields: []*FieldDiff{
  5833  							{
  5834  								Type: DiffTypeDeleted,
  5835  								Name: "File",
  5836  								Old:  "foo",
  5837  								New:  "",
  5838  							},
  5839  						},
  5840  					},
  5841  				},
  5842  			},
  5843  		},
  5844  		{
  5845  			Name: "Dispatch payload edited",
  5846  			Old: &Task{
  5847  				DispatchPayload: &DispatchPayloadConfig{
  5848  					File: "foo",
  5849  				},
  5850  			},
  5851  			New: &Task{
  5852  				DispatchPayload: &DispatchPayloadConfig{
  5853  					File: "bar",
  5854  				},
  5855  			},
  5856  			Expected: &TaskDiff{
  5857  				Type: DiffTypeEdited,
  5858  				Objects: []*ObjectDiff{
  5859  					{
  5860  						Type: DiffTypeEdited,
  5861  						Name: "DispatchPayload",
  5862  						Fields: []*FieldDiff{
  5863  							{
  5864  								Type: DiffTypeEdited,
  5865  								Name: "File",
  5866  								Old:  "foo",
  5867  								New:  "bar",
  5868  							},
  5869  						},
  5870  					},
  5871  				},
  5872  			},
  5873  		},
  5874  		{
  5875  			// Place holder for if more fields are added
  5876  			Name:       "DispatchPayload edited with context",
  5877  			Contextual: true,
  5878  			Old: &Task{
  5879  				DispatchPayload: &DispatchPayloadConfig{
  5880  					File: "foo",
  5881  				},
  5882  			},
  5883  			New: &Task{
  5884  				DispatchPayload: &DispatchPayloadConfig{
  5885  					File: "bar",
  5886  				},
  5887  			},
  5888  			Expected: &TaskDiff{
  5889  				Type: DiffTypeEdited,
  5890  				Objects: []*ObjectDiff{
  5891  					{
  5892  						Type: DiffTypeEdited,
  5893  						Name: "DispatchPayload",
  5894  						Fields: []*FieldDiff{
  5895  							{
  5896  								Type: DiffTypeEdited,
  5897  								Name: "File",
  5898  								Old:  "foo",
  5899  								New:  "bar",
  5900  							},
  5901  						},
  5902  					},
  5903  				},
  5904  			},
  5905  		},
  5906  	}
  5907  
  5908  	for i, c := range cases {
  5909  		t.Run(c.Name, func(t *testing.T) {
  5910  			actual, err := c.Old.Diff(c.New, c.Contextual)
  5911  			if c.Error && err == nil {
  5912  				t.Fatalf("case %d: expected errored", i+1)
  5913  			} else if err != nil {
  5914  				if !c.Error {
  5915  					t.Fatalf("case %d: errored %#v", i+1, err)
  5916  				} else {
  5917  					return
  5918  				}
  5919  			}
  5920  
  5921  			if !reflect.DeepEqual(actual, c.Expected) {
  5922  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  5923  					i+1, actual, c.Expected)
  5924  			}
  5925  		})
  5926  	}
  5927  }