The issue was a semantic bug in the code, which was exploited by an unanticipated usage pattern. It is hard to catch this kind of error with testing, since the programmer is usually the one writing the test.
It is also (often) hard to catch subtle semantic bugs in code review too, unless the reviewer is looking for a known pattern of semantic bug.
There are some interesting things happening as a result -- formal verification, etc., but I am hoping that we end up seeing the emergence of an insurance market on smart contract behavior...