anchor.diff:    ],
     ?assertEqual(Expected, Vips).
   
  -
   -endif.
  
positive.code:   end.
  
  handle_response(State, {ok, {{_HttpVersion, 200, _ReasonPhrase}, _Headers, Body}}) ->
    NewState = parse_json_to_vips(State, Body),
    {ok, NewState};
  handle_response(_State, Response) ->
    lager:debug("Bad HTTP Response: ~p", [Response]),
    {error, http_error}.
  
  prune_old_agents(AgentCache) ->
    map:filter(fun (#agent_cache{last_seen_time = LastSeenTime}) ->
                     Now = erlang:monotonic_time(seconds),
                     Now - LastSeenTime =< minuteman_config:agent_reregistration_threshold()
                 end, AgentCache).
  
  %% TODO(tyler) break up this long horrible function
  %% TODO(tyler) test agent_cache
  -spec(parse_json_to_vips(State :: #state{}, Data :: binary()) -> #state{}).
  parse_json_to_vips(State = #state{last_master_elected = LastMaster,
                                    last_master_elected_local_time = LastMasterTime,
                                    agent_cache = AgentCache},
                     Data) ->
    Parsed = jsx:decode(Data, [return_maps, {labels, atom}]),
    ElectedTime = maps:get(elected_time, Parsed),
    Now = erlang:monotonic_time(seconds),
  
    LastMasterCanKillOldAgents = Now - LastMasterTime > minuteman_config:agent_reregistration_threshold(),
    State2 = case {ElectedTime == LastMaster, LastMasterCanKillOldAgents} of
               {false, _} ->
                 State#state{last_master_elected = ElectedTime,
                             last_master_elected_local_time = Now};
               {true, true} ->
                 %% This master is old enough to have invalidated any tasks on
                 %% slaves that have not yet reconnected.
                 PrunedCache = prune_old_agents(AgentCache),
                 State#state{agent_cache = PrunedCache};
               {true, _} ->
                 %% This master is not old enough to trust with agent evictions
                 State
             end,
  
    Frameworks = maps:get(frameworks, Parsed),
    Agents = maps:get(slaves, Parsed),
    FrameworkTasks = frameworks_to_tasks(Frameworks),
  
    RawActiveAgents = lists:filter(fun(Agent) ->
                                       maps:get(active, Agent)
                                   end, Agents),
  
    %% Get a map from agent ID to #agent_cache{}, we will trust these active agents
    ActiveAgents = lists:foldl(fun(#{pid := Pid, id := Id}, AccIn) ->
                                   AgentTasks = lists:filter(fun (Task) ->
                                                                 TaskAgent = maps:get(slave_id, Task),
                                                                 TaskAgent == Id
                                                             end, FrameworkTasks),
                                   IP = libprocess_pid_to_ip(Pid),
                                   AccIn#{Id => #agent_cache{last_seen_time = Now,
                                                             last_seen_tasks = AgentTasks,
                                                             last_seen_ips = IP}}
                               end, #{}, RawActiveAgents),
  
    %% Merge cached tasks with tasks from active agents
    CachedAgents = State2#state.agent_cache,
    MergedAgentCache = maps:merge(CachedAgents, ActiveAgents),
    State3 = State2#state{agent_cache = MergedAgentCache},
  
negative.code: proper_test() ->
    [] = proper:module(?MODULE).
  
  %% TODO(tyler) prop: keep tasks from last mesos master if it just changed and is empty
  %% TODO(tyler) prop: remove tasks from last mesos master if it changed longer ago than the threshold
  %% TODO(tyler) prop: favor new tasks for an agent over cached agent tasks
  %% TODO(tyler) prop: keep tasks from agents that disappear for less than the threshold
  %% TODO(tyler) prop: remove tasks from agents that disappear for more than the threshold
  
  prop_cache_tasks_across_master_failover() ->
    ok.
  
  prop_valid_states_parse() ->
    ?FORALL(S, mesos_state(), parses(S)).
  
  